C#中的条件泛型类型构造函数?

Wil*_*sem 4 c# generics constructor wildcard

假设你有一个泛型类Foo:

public class Foo<T> {

    public T Data {
        get;
        protected set;
    }

}
Run Code Online (Sandbox Code Playgroud)

是否可以定义仅在T继承(或是)特定类型时才适用的构造函数.

比如说Tint:

    public Foo () {
        this.Data = 42;
    }
Run Code Online (Sandbox Code Playgroud)

应在编译时检查类型约束.这可能对优化很有用.比如说你有一个IEnumerable<T>并且你想做一个"缓存"(因为LINQ查询可能非常昂贵).现在如果IEnumerable<T>已经是IList<T>,那么不复制数据是有用的.另一方面,如果它实际上是LINQ查询,则另一个构造函数可以将数据存储在数组中.


作为一种解决方法,当然可以继承Foo(例如IntFoo)并在那里定义构造函数:

public class IntFoo : Foo<int> {

    public IntFoo () {
        this.Data = 42;
    }

}
Run Code Online (Sandbox Code Playgroud)

然而,这种方法的一个问题是private数据无法访问(或者必须进行数据处理protected).是否存在其他一些缺点,或者是否应该以这种方式对特定于类型的构造函数进行建模?

Jef*_*hao 11

你可以在这里申请一个技巧.它适用于许多场景.

internal static class FooHelper
{
    private static class DefaultData<T>
    {
        public static T Value = default(T);
    }

    static FooHelper()
    {
        DefaultData<int>.Value = 42;
        DefaultData<string>.Value = "Hello World";
    }

    // From @JeffreyZhao:
    //
    // Use a static method to trigger the static constructor automatically,
    // or we need to use RuntimeHelpers.RunClassConstructor to make sure
    // DefaultData is corrected initialized.
    //
    // The usage of RuntimeHelpers.RunClassConstructor is kept but commented.
    // Using GetDefault<T>() is a better approach since static Foo() would be
    // called multiple times for different generic arguments (although there's 
    // no side affect in this case).
    //
    // Thanks to @mikez for the suggestion.
    public static T GetDefault<T>()
    {
        return DefaultData<T>.Value;
    }
}

public class Foo<T>
{
    /* See the comments above.
    static Foo()
    {
        RuntimeHelpers.RunClassConstructor(typeof(FooHelper).TypeHandle);
    }
     */

    public T Data { get; protected set }

    public Foo()
    {
        Data = FooHelper.GetDefault<T>();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以为有限类型指定默认值,并将它们的结果保留为默认值.

这个技巧在实践中有几个变化.在我的项目中,我们使用泛型ITypeConverter<T>而不是内置TypeConverter来避免不必要的装箱:

public interface ITypeConverter<T>
{
    bool CanConvertTo<TTarget>();
    TTarget ConvertTo(T value);
}
Run Code Online (Sandbox Code Playgroud)

同样的技巧可以应用为:

public class LongConverter : ITypeConverter<long>
{
    private static class Op<TTarget>
    {
        public static Func<long, TTarget> ConvertTo;
    }

    static LongConverter()
    {
        Op<string>.ConvertTo = v => v.ToString();
        Op<DateTime>.ConvertTo = v => new DateTime(v);
        Op<int>.ConvertTo = v => (int)v;
    }

    public TTarget ConvertTo<TTarget>(T value)
    {
        return Op<TTarget>.ConvertTo(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

优雅,快速,干净.