非泛型类支持通用构造函数吗?

63

这是否表示它不被支持,还是说虽然被支持但需要进行一些技巧操作?

例如:

class Foo
{
  public Foo<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2)
  {
     ...
  }
}

泛型只在构造函数中使用,没有任何依赖于它们的字段/属性,我使用它(泛型)来强制实现 f1 和 f2 的类型相关性。

备注:我发现了解决方法--静态方法Create,但无论如何我都很好奇为什么我在直接方法上遇到问题。

3个回答

92
不,泛型构造函数不受支持,无论是在泛型类还是非泛型类中。同样,泛型事件、属性和终结器也不受支持。
偶尔我也认为它很方便,但是语法看起来会非常糟糕。例如,假设你有以下代码:
public class Foo<T> {}

public class Foo
{
    public Foo<T>() {}
}

你会怎样做

new Foo<string>()

应该调用非泛型类的通用构造函数,或者泛型类的普通构造函数?你必须以某种方式区分它们,而且会很混乱 :(

同样地,考虑一个泛型类中的通用构造函数:

public class Foo<TClass>
{
    public Foo<TConstructor>() {}
}

你如何调用构造函数?希望我们都能够达成一致:

new Foo<string><int>()

看起来相当难看...

所以,从语义上讲它偶尔会有用 - 但不幸的是,由此产生的丑陋感会抵消这一点。


你可以通过不允许同名的泛型和非泛型类来解决同名类的问题(说真的,C#允许这样吗?)。对于泛型类的泛型构造函数,我认为它并不太丑陋——它只是更高阶的泛型性。 - Peter Alexander
3
一个注记--泛型类中的构造函数是泛型的,因为这个类是泛型的。然而(根据你的回答),它不能指定额外的泛型参数。感谢你提供非常好的例子! - greenoldman
2
@Peter:不,同名类问题不是问题,因为尽管你可以通过类型参数个数进行类"重载",但不存在歧义。以Tuple为例。 - Jon Skeet
4
@macias:我不认为构造函数是泛型的,正是因为它没有引入任何额外的类型参数。你会认为List<T>.IndexOf是一个泛型方法吗,仅仅因为它在一个泛型类型中吗? - Jon Skeet
Peter,实际上类名重载是有用的,因为你可以将所有静态方法放在非泛型类中。在C++中,我总是有这样奇怪的对 MyClass<...> 和 MyClassFactory。在C#中,您可以拥有多个Tuple类,并且调用Tuple.Create(5,“hello”,4.5)是“干净的”。 - greenoldman
@Jon,是的,我认为这种方法是通用的。毕竟它们都适用于通用类型--因此它们是通用的。非通用的是Count--因为它不依赖于通用类型。 - greenoldman

35

不支持通用构造函数,但您可以通过定义一个通用的 static 方法来绕过此限制,该方法返回一个新的 Foo

class Foo
{
  public static Foo CreateFromFuncs<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2)
  {
     ...
  }
}

它的使用方法如下:

// create generic dependencies
var func1 = new Func<byte, string>(...);
var func2 = new Func<string, byte>(...);

// create nongeneric Foo from dependencies
Foo myFoo = Foo.CreateFromFuncs<byte, string>(func1, func2);

2

这里有一个实际例子,说明如何添加额外的构造函数类型参数以及解决方法。

我将介绍一个简单的RefCounted包装器,用于IDisposable

public class RefCounted<T> where T : IDisposable
{
    public RefCounted(T value)
    {
        innerValue = value;
        refCount = 1;
    }

    public void AddRef()
    {
        Interlocked.Increment(ref refCount);
    }

    public void Dispose()
    {
        if(InterlockedDecrement(ref refCount)<=0)
            innerValue.Dispose();
    }

    private int refCount;
    private readonly innerValue;
}

这看起来很好。但是迟早你会想要将 RefCounted<Control> 转换为 RefCounted<Button>,同时保持两个对象的引用计数,即只有在两个实例都被处理时才处理底层对象。

最好的方法是如果你可以像 C++ 开发人员一样编写代码。

public RefCounted(RefCounted<U> other)
{
    ...whatever...
}

但是C#不允许这样做。因此,解决方案是使用一些间接方法。
private readonly Func<T> valueProvider;
private readonly Action disposer;

private RefCounted(Func<T> value_provider, Action disposer)
{
    this.valueProvider = value_provider;
    this.disposer = disposer;
}

public RefCounted(T value) : this(() => value, value.Dispose)
{
}

public RefCounted<U> Cast<U>() where U : T 
{
    AddRef();
    return new RefCounted<U>(() => (U)(valueProvider()),this.Dispose);
}

public void Dispose(){
    if(InterlockedDecrement(ref refCount)<=0)
        disposer();
}

如果您的类中有任何泛型类型的字段,您只能将所有这些类型放入该类中。然而,如果您只想从构造函数中隐藏某些类型,则需要使用上述技巧 - 使用隐藏构造函数将所有内容组合在一起,并定义一个普通的泛型函数来调用该构造函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接