我可以将一个泛型设置为可选的,并默认为特定的类吗?

54
我的问题与C#泛型中的“默认”类型参数有关是否有合理的方法来处理C#泛型中的“默认”类型参数?,但使用内部泛型类时,这种方法行不通。
给定如下代码:
using System;

public class FooEventArgs<T> : EventArgs
{
    // ... T properties and a constructor
}

public class Foo<T>
{
    public delegate void EventHandler<FooEventArgs>(object sender, FooEventArgs<T> e);
    public event EventHandler<FooEventArgs<T>> Changed
}

当它被用作这样时:

public class User
{
    public Foo<int> foo1;
    public Foo<object> foo2;

    public User()
    {
        foo1 = new Foo<int>();
        foo2 = new Foo<object>();
        foo1.Changed += foo1_Changed;
        foo2.Changed += foo2_Changed;
    }

    protected void foo1_Changed(object sender, FooEventArgs<int> e) { ... }
    protected void foo2_Changed(object sender, FooEventArgs<object> e) { ... }
}

我希望如果可以使用泛型的可选项,这样在许多情况下,我就不需要知道某个东西将会是什么类型。(数据来自一个具有其自身变量类型的外部系统,这些类型然后被转换为.NET类型,但我遇到了一些情况,例如,一个远程数据类型可能会转换成几种.NET类型中的一种,或者它是"任意"类型 - 因此, 对于那种情况,object将是唯一的真正答案。)
我所想到的解决方案是子类化(这也是之前链接问题中的主要建议):
public class Foo : Foo<object>
{
    public Foo(...) : base(...) { }
}

public class FooEventArgs : FooEventArgs<object>
{
    public Foo(...) : base(...) { }
}

我希望您能像这样使用它:

public class User
{
    public Foo foo3;

    public User()
    {
        foo3 = new Foo();
        foo3.Changed += foo3_Changed;
    }

    protected void foo3_Changed(object sender, FooEventArgs e) { ... }
}

问题在于它不能自然地与接受FooEventArgsfoo3_Changed一起使用;它需要FooEventArgs<object>,因为这是Foo.Changed事件将传递给它的内容(因为该值将来自Foo<object>)。
Foo.cs(3,1415926): error CS0123: No overload for 'foo3_Changed' matches delegate 'FooLibrary.Foo<object>.EventHandler<FooLibrary.FooEventArgs<object>>'

除了复制整个类以外,我还能做些什么来解决这个问题吗?

我尝试了另一种方法:使用隐式运算符将FooEventArgs<object>转换为FooEventArgs

    public static implicit operator FooEventArgs(FooEventArgs<object> e)
    {
        return new FooEventArgs(...);
    }

很不幸,这似乎无法正常工作,尽管我不太清楚原因是什么。
EditBuffer.cs(13,37): error CS0553: 'FooLibrary.FooEventArgs.implicit operator FooLibrary.FooEventArgs(FooLibrary.FooEventArgs<object>)': user-defined conversions to or from a base class are not allowed

那么,我是否能够对此采取任何措施,或者我正确地认为这是“硬伤”,我只能使用FooEventArgs<object>(然后我想我可能会直接使用Foo<object>)?


Related - Shimmy Weitzhandler
2个回答

52

我刚刚发现的另一件有趣的事情是,您可以创建具有相同名称但不同签名的通用类。

class Foo<T> { 

}

class Foo<T,T> { 

}

然后您可以像下面这样调用它们中的任何一个:
new Foo<string>();
new Foo<int,double>();
new Foo<string,int>();

我认为有趣的是,尽管这两个类名称相同,但它们可以共存,因为它们具有不同的签名。

我猜这就是元组类的工作方式。

public class Tuple<T1, T2, T3... T8> 
{ 
...

4
好的回答。这就像重载方法的工作方式一样。 - mojoblanco
@mojoblanco 这在类级别上是相似的。 - K-Dawg
非常有用的答案,超越了这个问题。类重载确实有助于解决其他问题。 - Michael

51

说实话,我认为你不能做太多事情。你可以让Foo成为双重泛型:

public class Foo<TData, TArgs> where TArgs : FooEventArgs<TData>
{
    public delegate void EventHandler<TArgs>(object sender, TArgs e);
    public event EventHandler<TArgs> Changed;
}

然后您可以编写以下内容:
public class Foo : Foo<object, FooEventArgs>

...但是这确实让事情变得非常复杂,而收益却很少。

我认为,即使包含类型参数会更冗长一些,但它确实非常清晰明了——而继承可能会以各种方式使事情变得混乱。当您不是真正试图模拟行为专业化时,最好避免使用类继承。

顺便说一下,你的隐式转换无法工作与泛型无关——正如错误消息所述,您不能声明沿着继承层次结构向上或向下的转换(隐式或显式)。从C#规范第6.4.1节:

C#只允许声明某些用户定义的转换。特别地,不可能重新定义已经存在的隐式或显式转换。

(请参阅该部分获取更多详细信息。)


顺便说一句,我发现在泛型中,通常更常见的是使用接口来进行继承:

public interface IFoo
{
    // Members which don't depend on the type parameter
}

public interface IFoo<T> : IFoo
{
    // Members which all use T
}

这样,代码只需接收一个 IFoo 即可,无需担心泛型方面的问题,如果它们不需要知道 T

不幸的是,在您特定的情况下,这并没有帮助到您。


这是一个有趣的见解,关于处理接口继承的反向方式。但正如你所观察到的,在这种情况下行不通。谢谢。 - Chris Morgan
@ChrisMorgan:是的-我只是把它作为一个附带问题提出来。我已经编辑了答案的开头,加入了另一个想法,但这并不是我真正喜欢的想法。 - Jon Skeet
不,我也不喜欢它...好吧,那么明确一点,我认为是这样的。 - Chris Morgan
1
像往常一样,Jon的回答非常出色。 - pim

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