C# でのプリコンパイラ ディレクティブの削除
-
27-10-2019 - |
質問
私は、あまりレガシーではないコードを保守するように頼まれましたが、そのコードはコンパイラ指示子だらけで、ほとんど判読できなくても、保守可能とほぼ同じものになっています。適例:
#if CONDITION_1
protected override void BeforeAdd(LogEntity entity)
#else
protected override void BeforeAdd(AbstractBusinessEntity entity)
#endif
{
#if CONDITON_1
entity.DateTimeInsert = DateTime.Now;
#else
((LogEntity) entity).DateTimeInsert = DateTime.Now;
#endif
base.BeforeAdd(entity);
}
using
ディレクティブはさらに便利です:
#if CONDITION_1
using CompanyName.Configuration;
#endif
#if CONDITION_2||CONDITION_1
using CompanyName.Data;
using CompanyName.Data.SqlBuilders;
#else
using CompanyName.Legacy.Database;
using CompanyName.Legacy.Database.SQLBuilders;
using CompanyName.Legacy.Database.SQLBuilders.parameterTypes;
#endif
をあげようと思ったのですが、 ConditionalAttribute
やってみますが、この状況ではうまくいきません
このコンパイラ指令の悪夢から抜け出す方法はあるでしょうか?
コードは以下に対してコンパイルされます .NET 3.5
.
アップデート:
Oded は、周囲のコンパイラ ディレクティブを削除することを提案すると答えました。 BeforeAdd
したがって、メソッドをオーバーロードします。残念ながら、両方のメソッドがオーバーライドすることになっているため、これは機能しません。 AbstractBusiness
このクラスは、どのアセンブリが最終的に含まれるかに応じて 2 つの異なる実装を提供します。
protected virtual void BeforeAdd(TEntity entity) {}
または
protected virtual void BeforeAdd(AbstractBusinessEntity entity) {}
このコードは、会社が過去のある時点で作成した一連のライブラリから依存関係を取得し、それ以来「アップグレード」されています。現在、名前空間が衝突し、実装が異なるライブラリ セットの 4 つの異なるバージョンがあります。すべては、(非常に)古いバージョンを使用するアプリケーションとの「下位互換性」の名の下にあります。
結論
一般的なアプローチとして最も理にかなっているため、最終的に@Odedの答えを選択しました(キス。 そしてそのすべて)。ただし、この場合は使用できませんでした。ここで見られるものは氷山の一角にすぎません。K.I.S.Sはしたくない。もしお金を払ってくれたらこのコード。
解決
最初のケースでは、この構造体の代わりにメソッドのいくつかのオーバーロードを単純に使用できるように見えます。この時点では、オーバーロードの解決によって問題が解決されるはずです。
2 番目のケース (ディレクティブを使用) - 必要に応じてエイリアスを使用して、一部のディレクティブにエイリアスを付け、すべてのディレクティブを含めることができます。すべての名前空間が含まれる場合はどうなりますか?名前の衝突はありますか?
他のヒント
問題はこのクラスにあるわけではないと私は主張します。このクラスは単なる症状です。問題は、BeforeAdd を呼び出す基本クラスにあります。そこでリファクタリングできれば、条件付きコンパイルは必要ありません。
名前と名前空間が競合する場合は、using キーワード (アセンブリのキーワードではなく) を使用して回避できます。
したがって、次のようなことができます
using LegacyLogEntity = Some.Fully.Qualified.Namespace.LogEntity;
using SomeOtherLogEntity = Some.Other.Fully.Qualified.Namespace.CurrentLogEntity;
// ..
LegacyLogEntity entity = new LegacyLogEntity();
また、問題はこのクラス自体ではなく、基本クラスにあると思います。
その場合は、適応またはインターフェイスを使用することで、このナンセンスを回避できます。
他のクラスの名前はわかりませんが、EntityAggregator と呼ぶことにします。
public interface IEntity {
DateTime InsertionTime { get; set; }
}
次に、アグリゲータの基本クラスで次のようにします。
protected virtual void BeforeAdd(IEntity entity)
{ // whatever
}
次に、サブクラスで次のようにします。
protected override void BeforeAdd(IEntity entity)
{
entity.DateTime = DateTime.Now;
base.BeforeAdd(entity);
}
これで、そのインターフェイスを実装することで、他のオブジェクトを IEntity に適合させることができます。
このコードを見ると、このコードの代わりにイベントを使用しているのではないかという気もします。
ここで、コードが 2 つの異なる条件下で 2 つの別々の場所でコンパイルされる、複数回使用のコンパイルについて話している場合、部分クラスを使用することで、より適切にそれを行うことができます。
CONDITION_1 コードを次のように分離します。
// in file WhateverYourClassIs.condition1.cs
#if !CONDITION_1
#error this file should never be included in a build WITHOUT CONDITION_1 set
#endif
public partial class WhateverYourClassIs {
protected override void BeforeAdd(LogEntity entity) {
entity.DateTimeInsert = DateTime.Now;
base.BeforeAdd(entity);
}
}
// in file WhateverYourClassIs.NotCondition1.cs
#if CONDITION_1
#error this file should never be included in a build WITH CONDITION_1 set
#endif
public partial class WhateverYourClassIs {
protected override void BeforeAdd(AbstractBusinessEntity entity) {
((LogEntity)entity).DateTimeInsert = DateTime.Now;
base.BeforeAdd(entity);
}
}
この場合、コードが繰り返されるため、これは好きではありません。using キーワードを使用すると、これを支援できます。
#if CONDITION_1
using MyAbstractBusinessEntity = LogEntity;
#else
using MyAbstractBusinessEntity = AbstractBusinessEntity;
#endif
// ...
protected override void BeforeAdd(MyAbstractBusinessEntity entity)
{
// in CONDITION_1, the case is a no-op
((LogEntity)entity).DateTimeInsert = DateTime.Now;
base.BeforeAdd(entity);
}
私が見ているところによると、元の開発者には継承とポリモーフィズムの感覚がまったくなかったようです。コードから判断するのは少し難しいですが、LogEntity と AbstractBusinessEntity は共通のプロパティを共有しているようです。継承モデルはあるのでしょうか、それとも 2 つのクラスはまったく無関係なのでしょうか?それらに関連性がない場合、両方が実装できる継承モデルまたはインターフェイスを作成できますか?クラスを貼り付けておくと役立つかもしれません。
簡単に言うと、私はそのコードを現在の形式で操作するのに時間を無駄にするつもりはありません。私なら、どんな犠牲を払ってでも、コンパイラ ディレクティブを削除する方法を見つけます。完全に回復できないわけではありませんが、多少の努力が必要になる可能性があります。
それが実用的かどうかはわかりませんが、これを処理するために DVCS、Mercurial にブランチを作成することになります。
2 つのブランチを実行し、バグを修正したり一般的なコードを追加したりする間、一時的に 3 つ目のブランチを実行します。
初期バージョンの作成方法は次のとおりです。
5---6---7 <-- type 1 of library
/
1---2---3---4
\
8---9--10 <-- type 2 of library
そのうちの 1 つのみのバグを修正するには:
5---6---7--11 <-- bugfix or change only to type 1
/
1---2---3---4
\
8---9--10
よくあるバグを修正するには:
5---6---7--11--13--15 <-- merged into type 1
/ /
1---2---3---4--11--12---+-------+ <-- common fix(es)
\ \
8---9--10--14 <-- merged into type 2
注記:これは、型ブランチでも共通ブランチでも強引なリファクタリングを行わないことを前提としています。そうすれば、少なくともこのようなブランチの多い方法と比較すると、おそらく現在の状況を改善できるでしょう。このようなリファクタリングを行うと、将来のマージが行われる可能性があります 本当に 痛い。