0

CodeDom とプレーン コード文字列を使用して、Visual Studio 拡張機能でコードを生成しています。私の拡張機能は、リフレクションを使用して現在のクラスで宣言されたフィールドとプロパティを読み取り、コンストラクター、イニシャライザーを生成し、特定のインターフェイスなどを実装します。

ジェネレーター クラスは単純です。

public class CodeGenerator < T >  
{  
    public string GetCode ()  
    {  
        string code = "";  
        T type = typeof(T);  
        List < PropertyInfo > properties = t.GetProperties();  
        foreach (PropertyInfo property in properties)  
            code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")";  
    }  
}

私は 2 つの方法でフィールドとプロパティの初期化子に行き詰まっています。

まず、default(AnyNonGenericValueOrReferenceType)ほとんどの場合はうまくいくように見えますが、生成されたコードでそれを使用することに不快感を覚えます。

次に、ジェネリック型の基になる型を取得する方法が見つからないため、ジェネリック型では機能しません。プロパティが の場合List < int >、 をproperty.PropertyType.Name返しますList`1。ここには 2 つの問題があります。まず、文字列操作を使用せずにジェネリック型の適切な名前を取得する必要があります。次に、基になる型にアクセスする必要があります。完全なプロパティ タイプ名は、次のようなものを返します。

System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
4

2 に答える 2

2

答えようとする前に、あなたがしていることは冗長に思えることを指摘しなければならないと感じています。このコードをコンストラクターに入れていると仮定すると、次のようなものが生成されます。

public class Foo
{
  private int a;
  private bool b;
  private SomeType c;

  public Foo()
  {
    this.a = default(int);
    this.b = default(bool);
    this.c = default(SomeType);
  }
}

不要です。クラスが構築されると、それはすでに自動的に行われています。(実際、いくつかの簡単なテストでは、これらの割り当てがコンストラクターで明示的に行われた場合、これらの割り当てが最適化されていないことが示されていますが、JITter がそれを処理できると思います。)

第二に、defaultキーワードは大部分があなたがしていることを正確に行うように設計されています: コンパイル時にタイプが不明な変数に「デフォルト」値を割り当てる方法を提供するためです。ジェネリックコードで使用するために導入されたと思いますが、自動生成されたコードはそれを使用する上でも確かに正しいです。

default参照型の値は であるnullことを覚えておいてください。

this.list = default(List<int>);

新しい を構築せずList<int>、 に設定this.listするだけnullです。代わりに、Type.IsValueTypeプロパティを使用して値の型をデフォルト値のままにし、を使用して参照型を初期化することをお勧めしますnew

最後に、ここで探しているのは、クラスのIsGenericTypeプロパティと対応するメソッドだと思います。TypeGetGenericArguments()

foreach (PropertyInfo property in properties)  
{
  if (property.Type.IsGenericType)
  {
    var subtypes = property.Type.GetGenericArguments();
    // construct full type name from type and subtypes.
  }
  else
  {
    code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")";  
  }
}

編集:

参照型に役立つものを構築する限り、生成されたコードで使用されている一般的な手法は、使用するクラスにパラメーターなしのコンストラクターを要求することです。クラスにパラメーターなしのコンストラクターがあるかどうかを確認するType.GetConstructor()のは簡単Type[]です。それが確立されたら、単に置き換えるだけで必要なものが得られます。Type.EmptyTypesConstructorInfonulldefault(typename)new typename()

より一般的には、そのメソッドに任意の型の配列を指定して、一致するコンストラクターがあるかどうかを確認したり、呼び出しGetConstructors()てそれらすべてを取得したりできます。ここで注目すべきは、 のIsPublicIsStatic、およびIsGenericMethodフィールドで、ConstructorInfoこのコードが生成されている場所から実際に呼び出すことができるものを見つけます。

ただし、解決しようとしている問題は、なんらかの制約を設定できない限り、勝手に複雑になります。1 つのオプションは、任意のコンストラクターを見つけて、次のような呼び出しを作成することです。

var line = "this." + fieldName + " = new(";
foreach ( var param in constructor.GetParameters() )
{
  line += "default(" + param.ParameterType.Name + "),";
}
line = line.TrimEnd(',') + ");"

(これは説明のみを目的としていることに注意してください。ここではおそらく CodeDOM を使用するか、少なくとも StringBuilder を使用します:)

しかしもちろん、各パラメーターの適切な型名を決定するという問題があり、それ自体がジェネリックである可能性があります。また、参照型パラメーターはすべて null に初期化されます。また、選択できる任意の数のコンストラクターのどれが実際に使用可能なオブジェクトを生成するかを知る方法はありません (インスタンスを構築した直後にプロパティを設定したり、メソッドを呼び出したりすると仮定するなど、悪いことをするコンストラクターもあります)。

これらの問題をどのように解決するかは、技術的なものではありません。これと同じロジックを、各パラメーターに好きなだけ再帰的に適用できます。ユースケースについて、どれだけ複雑にする必要があるか、ユーザーにどのような制限を課すかを決定することが重要です。

于 2012-01-10T18:43:57.910 に答える
1

文字列を使用することが確実な場合は、それらの型名をフォーマットする独自のメソッドを作成する必要があります。何かのようなもの:

static string FormatType(Type t)
{
    string result = t.Name;

    if (t.IsGenericType)
    {
        result = string.Format("{0}<{1}>",
            result.Split('`')[0],
            string.Join(",", t.GetGenericArguments().Select(FormatType)));
    }

    return result;
}

このコードは、ファイルに必要な がすべて含まれていることを前提としていusingます。

しかし、CodeDOM のオブジェクト モデルを実際に使用する方がはるかに優れていると思います。usingこのように、 s、書式設定タイプ、タイプミスについて心配する必要はありません。

var statement =
    new CodeAssignStatement(
        new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), property.Name),
        new CodeDefaultValueExpression(new CodeTypeReference(property.PropertyType)));

本当に使いたくない場合default(T)は、型が参照型か値型かを調べることができます。参照型の場合は、 を使用しますnull。値型の場合、デフォルトのコンストラクターが存在する必要があるため、それを呼び出すことができます。

于 2012-01-10T18:43:27.707 に答える