我可以制作一个通用的可选项,默认为某个类吗?

Chr*_*gan 27 c# generics .net-3.5

我的问题与C#Generics中的"默认"类型参数有合理的方法有关吗?,但使用内部泛型类,该方法不起作用.

给出这样的代码:

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
}
Run Code Online (Sandbox Code Playgroud)

并使用它像这样:

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) { ... }
}
Run Code Online (Sandbox Code Playgroud)

好吧,如果我可以使用通用可选项,我宁愿喜欢它,因为在很多情况下我不知道会出现什么类型的东西.(数据来自外部系统,它有自己的变量类型,然后转换为.NET类型,但我遇到这样的情况,例如,一个远程数据类型可能变成几种.NET类型之一,或者它是"任何"类型 - 因此object将是这个案子唯一真正的答案.)

我立即想到的解决方案是子类化(这也是与之前相关的问题中的主要建议):

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

public class FooEventArgs : FooEventArgs<object>
{
    public Foo(...) : base(...) { }
}
Run Code Online (Sandbox Code Playgroud)

然后我想像这样使用它:

public class User
{
    public Foo foo3;

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

    protected void foo3_Changed(object sender, FooEventArgs e) { ... }
}
Run Code Online (Sandbox Code Playgroud)

问题是它自然不会foo3_Changed接受FooEventArgs; 它需要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>>'
Run Code Online (Sandbox Code Playgroud)

有什么我可以做的,没有重复大部分课程吗?

我确实尝试了另一件事:一个隐式运算符转换FooEventArgs<object>FooEventArgs.

    public static implicit operator FooEventArgs(FooEventArgs<object> e)
    {
        return new FooEventArgs(...);
    }
Run Code Online (Sandbox Code Playgroud)

不幸的是,这似乎不起作用,虽然我不清楚为什么:

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
Run Code Online (Sandbox Code Playgroud)

那么,再一次,我有什么可以做的,或者我认为这是艰难的运气,我只需要满足使用FooEventArgs<object>(然后我想我也可以使用Foo<object>)?

Jon*_*eet 35

老实说,我认为你无能为力.你可以做出Foo双重通用:

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

然后你可以写:

public class Foo : Foo<object, FooEventArgs>
Run Code Online (Sandbox Code Playgroud)

......但它确实让事情变得非常复杂,但收效甚微.

我还要说,即使包含类型参数更加冗长,但它确实非常清楚 - 而继承可以通过各种方式使水变得混乱.当你没有真正尝试模拟行为专业化时,我会避开类继承.

隐式转换不起作用的原因与泛型无关,顺便说一下 - 正如错误消息所述,您无法声明在继承层次结构中上下移动的转换(隐式或显式).从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
}
Run Code Online (Sandbox Code Playgroud)

这样,如果代码IFoo不需要知道,代码就可以只收到一个而不用担心泛型方面T.

不幸的是,在您的具体情况下,这对您没有帮助.


Pri*_*ign 24

我刚发现的另一个有趣的事情是你可以创建具有相同名称但签名不同的泛型类.

class Foo<T> { 

}

class Foo<T,T> { 

}
Run Code Online (Sandbox Code Playgroud)

然后你可以调用其中一个如下:

new Foo<string>();
new Foo<int,double>();
new Foo<string,int>();
Run Code Online (Sandbox Code Playgroud)

我只是觉得有趣的是,尽管两个类具有相同的名称,但它们可以共存,因为它们具有不同的签名.

我猜这就是Tuple类的工作方式

  • 好答案。它与重载方法的工作原理类似 (4认同)