18

次のようなコードがある場合:

static T GenericConstruct<T>() where T : new()
{
    return new T();
}

C# コンパイラは、Activator.CreateInstance への呼び出しを発行することを要求しますが、これはネイティブ コンストラクターよりもかなり低速です。

次の回避策があります。

public static class ParameterlessConstructor<T>
    where T : new()
{
    public static T Create()
    {
        return _func();
    }

    private static Func<T> CreateFunc()
    {
        return Expression.Lambda<Func<T>>( Expression.New( typeof( T ) ) ).Compile();
    }

    private static Func<T> _func = CreateFunc();
}

// Example:
// Foo foo = ParameterlessConstructor<Foo>.Create();

しかし、なぜこの回避策が必要なのか、私には理解できません。

4

5 に答える 5

9

JITtingの問題だと思います。現在、JIT はすべての参照型引数に対して同じ生成コードを再利用します。そのため、List<string>の vtable は と同じマシン コードを指しList<Stream>ます。new T()JITted コードで各呼び出しを解決する必要がある場合、それは機能しません。

単なる推測ですが、ある程度の意味はあります。

興味深い点が 1 つあります。どちらの場合も、値型のパラメーターなしのコンストラクターが存在する場合は呼び出されません (これは非常にまれです)。詳細については、私の最近のブログ投稿を参照してください。式ツリーでそれを強制する方法があるかどうかはわかりません。

于 2008-12-15T07:00:57.873 に答える
8

これは、T が値型か参照型かが明確でないためと考えられます。非一般的なシナリオでこれら 2 つのタイプを作成すると、非常に異なる IL が生成されます。このあいまいさに直面して、C# は型作成の普遍的な方法を使用することを余儀なくされています。Activator.CreateInstance は法案に適合します。

簡単な実験は、この考えを支持するように見えます。次のコードを入力して IL を調べると、型にあいまいさがないため、CreateInstance ではなく initobj が使用されます。

static void Create<T>()
    where T : struct
{
    var x = new T();
    Console.WriteLine(x.ToString());
}

クラスと new() 制約に切り替えると、それでも Activator.CreateInstance が強制されます。

于 2008-12-15T07:04:51.657 に答える
3

なぜこの回避策が必要なのですか?

new()ジェネリック制約が.NET 2.0のC#2.0に追加されたためです。

一方、Expression<T>とその仲間は.NET3.5に追加されました。

したがって、.NET 2.0では不可能だったため、回避策が必要です。一方、(1)Activator.CreateInstance()の使用は可能であり、(2)ILには「newT()」を実装する方法がないため、Activator.CreateInstance()を使用してその動作を実装しました。

于 2009-06-10T19:12:26.360 に答える
2

式は 1 回だけコンパイルされるため、これは少し高速です。

public class Foo<T> where T : new()
{
    static Expression<Func<T>> x = () => new T();
    static Func<T> f = x.Compile();

    public static T build()
    {
        return f();
    }
}

パフォーマンスを分析すると、この方法は、より冗長なコンパイル式と同じくらい高速であり、new T()(私のテスト PC では 160 倍高速) よりもはるかに高速です。

パフォーマンスを少し向上させるために、ビルド メソッドの呼び出しを省略し、代わりにファンクターを返すことができます。これにより、クライアントはキャッシュして直接呼び出すことができます。

public static Func<T> BuildFn { get { return f; } }
于 2009-08-15T00:38:22.827 に答える
2

興味深い観察:)

ソリューションのより簡単なバリエーションを次に示します。

static T Create<T>() where T : new()
{
  Expression<Func<T>> e = () => new T();
  return e.Compile()();
}

明らかに素朴です(そして遅い可能性があります):)

于 2008-12-15T07:17:14.643 に答える