質問
C# の仕様にはプリプロセッサと基本的なディレクティブ (#define、#if など) が含まれていますが、この言語には C/C++ などの言語にあるような柔軟なプリプロセッサがありません。このような柔軟なプリプロセッサがないのは、Anders Hejlsberg による設計上の決定だったと思います (ただし、残念ながら、これについての言及は現在見つかりません)。経験上、これは確かに良い決断です。私が C/C++ をたくさんやっていた頃には、非常にひどい保守不可能なマクロがいくつか作成されていたからです。
とはいえ、もう少し柔軟なプリプロセッサが役立つシナリオは数多くあります。次のようなコードは、いくつかの単純なプリプロセッサ ディレクティブによって改善される可能性があります。
public string MyProperty
{
get { return _myProperty; }
set
{
if (value != _myProperty)
{
_myProperty = value;
NotifyPropertyChanged("MyProperty");
// This line above could be improved by replacing the literal string with
// a pre-processor directive like "#Property", which could be translated
// to the string value "MyProperty" This new notify call would be as follows:
// NotifyPropertyChanged(#Property);
}
}
}
このような非常に単純なケースを処理するプリプロセッサを作成することは良い考えでしょうか?スティーブ・マコーネルが書いた コードの完成 (p208):
独自のプリプロセッサを作成する 言語にプリプロセッサが含まれていない場合、プリプロセッサを作成するのは非常に簡単です。
私は引き裂かれました。このような柔軟なプリプロセッサを C# から除外するのは設計上の決定でした。ただし、私が非常に尊敬する著者は、状況によっては大丈夫かもしれないと述べています。
C# プリプロセッサを構築する必要がありますか?私がやりたいことを簡単に実行できるものはありますか?
解決
次のようなアスペクト指向のソリューションを検討してみてください。 ポストシャープ, 、カスタム属性に基づいて事後にコードを挿入します。これはプリコンパイラーの逆ですが、探している機能 (PropertyChanged 通知など) を提供できます。
他のヒント
C# プリプロセッサを構築する必要がありますか?私がやりたいことを簡単に実行できるものはありますか?
いつでも C プリプロセッサを使用できます。構文的には C# が十分に近いです。M4もオプションです。
短いコードがエレガントなコードと同じだと思っている人が多いと思いますが、それは真実ではありません。
あなたが提案した例は、あなたが示したようにコードで完全に解決されていますが、何にプリプロセッサディレクティブが必要ですか?コードを「前処理」するのではなく、コンパイラーにプロパティにコードを挿入してもらいたいのです。これは一般的なコードですが、それはプリプロセッサの目的ではありません。
あなたの例では、どこに制限を設定しますか?これは明らかにオブザーバー パターンを満たしており、役立つことは間違いありませんが、コードが提供するため、実際に実行される役立つことがたくさんあります。 柔軟性 ただし、プリプロセッサはそうではありません。プリプロセッサ ディレクティブを通じて一般的なパターンを実装しようとすると、言語自体と同じくらい強力なプリプロセッサが必要になります。あなたがしたい場合は プリプロセッサ ディレクティブを使用してコードを別の方法で処理する ただし、コード スニペットが必要なだけの場合は、プリプロセッサがそれを行うように意図されていないため、別の方法を見つけてください。
C++ スタイルのプリプロセッサを使用すると、OP のコードは次の 1 行に減らすことができます。
OBSERVABLE_PROPERTY(string, MyProperty)
OBSERVABLE_PROPERTY は次のようになります。
#define OBSERVABLE_PROPERTY(propType, propName) \
private propType _##propName; \
public propType propName \
{ \
get { return _##propName; } \
set \
{ \
if (value != _##propName) \
{ \
_##propName = value; \
NotifyPropertyChanged(#propName); \
} \
} \
}
処理するプロパティが 100 個ある場合、コードの行数は約 1,200 行になります。~100。どちらが読みやすく理解しやすいでしょうか?どちらが書きやすいでしょうか?
C# では、カット アンド ペーストして各プロパティを作成すると仮定すると、プロパティごとに 8 回のペースト、合計 800 回のペーストになります。マクロを使えば貼り付けは一切不要です。コーディングエラーが含まれる可能性が高いのはどれですか?たとえば、次のように追加する必要がある場合は、どちらを変更する方が簡単です。IsDirtyフラグ?
かなりの場合にカスタムのバリエーションが存在する可能性がある場合、マクロはあまり役に立ちません。
他のツールと同様に、マクロも悪用される可能性があり、悪用されると危険な場合もあります。一部のプログラマーにとって、これは宗教的な問題であり、あるアプローチが別のアプローチより優れているかどうかは無関係です。そういう人はマクロを避けるべきです。非常に鋭利なツールを定期的に、巧みに、そして安全に使用する私たちにとって、マクロはコーディング中だけでなく、下流のデバッグやメンテナンス中にも生産性を即座に向上させることができます。
C# 用のプリプロセッサの構築に反対する主な議論は、Visual Studio への統合です。インテリセンスと新しいバックグラウンド コンパイルをシームレスに動作させるには、(可能であれば) 多大な努力が必要になります。
代わりに、次のような Visual Studio 生産性プラグインを使用することもできます。 リシャープ または コードラッシュ。後者は、私の知る限りでは、比類のないテンプレート システムを備えており、優れた機能が付属しています。 リファクタリング 道具。
あなたが言及している問題の正確な種類を解決するのに役立つもう 1 つのものは、次のような AOP フレームワークです。 ポストシャープ.
その後、カスタム属性を使用して共通の機能を追加できます。
現在実行されているメソッドの名前を取得するには、スタック トレースを確認します。
public static string GetNameOfCurrentMethod()
{
// Skip 1 frame (this method call)
var trace = new System.Diagnostics.StackTrace( 1 );
var frame = trace.GetFrame( 0 );
return frame.GetMethod().Name;
}
プロパティ設定メソッドを使用している場合、名前は set_Property です。
同じ手法を使用して、ソース ファイルと行/列情報をクエリすることもできます。
ただし、プロパティ セットごとにスタックトレース オブジェクトを 1 回作成するのは時間がかかりすぎる可能性があるため、ベンチマークは実行しませんでした。
INotifyPropertyChanged を実装するときに、問題の重要な部分が 1 つ欠けている可能性があると思います。消費者はプロパティ名を決定する方法を必要としています。このため、プロパティ名を定数または静的な読み取り専用文字列として定義する必要があります。こうすることで、コンシューマーはプロパティ名を「推測」する必要がなくなります。プリプロセッサを使用した場合、コンシューマーはどのようにしてプロパティの文字列名を知るのでしょうか?
public static string MyPropertyPropertyName
public string MyProperty {
get { return _myProperty; }
set {
if (!String.Equals(value, _myProperty)) {
_myProperty = value;
NotifyPropertyChanged(MyPropertyPropertyName);
}
}
}
// in the consumer.
private void MyPropertyChangedHandler(object sender,
PropertyChangedEventArgs args) {
switch (e.PropertyName) {
case MyClass.MyPropertyPropertyName:
// Handle property change.
break;
}
}
私が C# の次のバージョンを設計しているとしたら、クラス名と関数名を保持するローカル変数が自動的に組み込まれる各関数について考えるでしょう。ほとんどの場合、コンパイラのオプティマイザがそれを取り除きます。
ただ、そのような需要がそれほどあるかどうかはわかりません。
@ホルヘはこう書きました:コードを別の方法で処理したい場合は、プリプロセッサ ディレクティブを使用しますが、コード スニペットが必要なだけの場合は、プリプロセッサはそのように意図されていないため、別の方法を見つけてください。
面白い。私は、プリプロセッサが必ずしもこのように動作するとは考えていません。提供されている例では、単純なテキスト置換を行っています。これは、プリプロセッサの定義とインラインです。 ウィキペディア.
これがプリプロセッサの適切な使用法ではない場合、通常はコンパイル前に行う必要がある単純なテキスト置換を何と呼ぶべきでしょうか?
少なくとも、提供されたシナリオでは、プリプロセッサを構築するよりもクリーンでタイプセーフなソリューションがあります。
ジェネリック医薬品を使用します。そのようです:
public static class ObjectExtensions
{
public static string PropertyName<TModel, TProperty>( this TModel @this, Expression<Func<TModel, TProperty>> expr )
{
Type source = typeof(TModel);
MemberExpression member = expr.Body as MemberExpression;
if (member == null)
throw new ArgumentException(String.Format(
"Expression '{0}' refers to a method, not a property",
expr.ToString( )));
PropertyInfo property = member.Member as PropertyInfo;
if (property == null)
throw new ArgumentException(String.Format(
"Expression '{0}' refers to a field, not a property",
expr.ToString( )));
if (source != property.ReflectedType ||
!source.IsSubclassOf(property.ReflectedType) ||
!property.ReflectedType.IsAssignableFrom(source))
throw new ArgumentException(String.Format(
"Expression '{0}' refers to a property that is not a member of type '{1}'.",
expr.ToString( ),
source));
return property.Name;
}
}
これを簡単に拡張して、 PropertyInfo
代わりに、プロパティの名前だけでなく、より多くの情報を取得できるようになります。
それは Extension method
, 、このメソッドは事実上すべてのオブジェクトで使用できます。
また、これはタイプセーフです。
それをいくら強調しても足りません。
(古い質問であることは承知していますが、実用的な解決策が欠けていることがわかりました。)