C#: 如何声明和使用一个包含不同类型的泛型类列表?

29

有以下通用类,它可以包含string、int、float、long中的任何一种类型:

public class MyData<T>
{
    private T _data;

    public MyData (T value)
    {
        _data = value;
    }

    public T Data { get { return _data; } }
}

我正在尝试获取一个列表,其中每个项目都是不同的T类型的MyData。我想要能够访问列表中的项并像以下代码一样获取其值:
MyData<> myData = _myList[0];    // Could be <string>, <int>, ...
SomeMethod (myData.Data);

其中SomeMethod()的声明如下:

public void SomeMethod (string value);
public void SomeMethod (int value);
public void SomeMethod (float value);

更新:

SomeMethod() 是来自另一个我无法控制的层级类,而 SomeMethod(object) 不存在。


然而,我似乎找不到让编译器满意的方法。

有什么建议吗?

谢谢。


你能详细说明一下你的SomeMethod是做什么的吗?这样我们或许可以更好地帮助你。 - Hosam Aly
这里不相关 SomeMethod 函数的作用。我只想补充一下,没有 SomeMethod(object) 的定义。 - Stécy
你的基本设计有缺陷。我建议你提出一个问题,详细说明要求和你的解决方案,并寻求建议。 - user1228
9个回答

14
我认为你遇到的问题是因为你试图创建一个通用类型,然后创建该通用类型的列表。你可以通过将你尝试支持的数据类型外包为一个IData元素来实现你想要做的事情,然后使用IData作为MyData泛型的约束条件。这样做的缺点是你需要创建自己的数据类型来表示所有你使用的基本数据类型(例如字符串、整数、浮点数、长整数)。它可能看起来像这样:
public class MyData<T, C>
    where T : IData<C>
{
    public T Data { get; private set; }

    public MyData (T value)
    {
         Data = value;
    }
}

public interface IData<T>
{
    T Data { get; set; }
    void SomeMethod();
}

//you'll need one of these for each data type you wish to support
public class MyString: IData<string>
{
   public MyString(String value)
   {
       Data = value;
   }

   public void SomeMethod()
   {
       //code here that uses _data...
       Console.WriteLine(Data);
   }

   public string Data { get; set; }
}

然后你的实现将类似于以下内容:
var myData = new MyData<MyString, string>(new MyString("new string"));    
// Could be MyString, MyInt, ...
myData.Data.SomeMethod();

这需要一些额外的工作,但可以实现你想要的功能。

更新:从你的接口中移除SomeMethod,然后只需这样做

SomeMethod(myData.Data.Data);

很遗憾,我需要包装原始类型,但我愿意接受它,因为似乎没有其他办法。然而,在你的例子中,你假设SomeMethod()是MyData的一部分,但实际上并不是这样(我已经更新了问题)。 - Stécy
实际上你可以完成同样的事情,我只是封装了一下,这样会更加封装化,可以看看我的更新。 - Joseph
那么,如何将MyData <MyString,string>和MyData <MyFloat,float>放入同一集合中? - Amy B
你需要创建另一个接口,比如说IDataType,并重新定义IData<T>接口为IData<T> where T : IDataType。然后每个具体类型也必须实现IDataType接口,例如class MyString: IData<string>, IDataType。 - Joseph
然后你可以这样实例化一个列表:new List<IDataType>(); 并且只要两者都是IDataType类型,就可以传入MyData<MyString, string>和MyData<MyFloat, float>。 - Joseph

9

委托可以真正帮助简化这个过程,同时仍然保持类型安全:

public void TestMethod1()
{
    Action<SomeClass, int> intInvoke = (o, data) => o.SomeMethod(data);
    Action<SomeClass, string> stringInvoke = (o, data) => o.SomeMethod(data);

    var list = new List<MyData> 
    {
        new MyData<int> { Data = 10, OnTypedInvoke = intInvoke },
        new MyData<string> { Data = "abc", OnTypedInvoke = stringInvoke }
    };

    var someClass = new SomeClass();
    foreach (var item in list)
    {
        item.OnInvoke(someClass);
    }
}

public abstract class MyData
{
    public Action<SomeClass> OnInvoke;
}

public class MyData<T> : MyData
{
    public T Data { get; set; }
    public Action<SomeClass, T> OnTypedInvoke 
    { set { OnInvoke = (o) => { value(o, Data); }; } }
}

public class SomeClass
{
    public void SomeMethod(string data)
    {
        Console.WriteLine("string: {0}", data);
    }

    public void SomeMethod(int data)
    {
        Console.WriteLine("int: {0}", data);
    }
}

非常棒的启示。 - Telemat

6

只需使用ArrayList,不需要考虑MyData<T>类型。

ArrayList myStuff = getStuff();
float x = myStuff.OfType<float>().First();
SomeMethod(x);
string s = myStuff.OfType<string>().First();
SomeMethod(s);
< p > MyData<T> 的问题在于您希望编译器检查仅在运行时才知道的类型。编译器只能检查在编译时已知的类型。


谢谢!这是解决我个人问题的答案。 我试图跟踪BlockingCollection <Interface>列表,然后通过GetQueue <T> where T:Interface方法检查是否在我的列表中已经存在类型为T的队列,如果没有,实例化并返回新的空队列。 但是,即使T:Interface,BlockingCollection<T>也无法被隐式转换为BlockingCollection<Interface>,而且我始终找不到正确的协变/逆变/委托实现来做到这一点。 - Isaac

4

你无法按照你想要的方式做。

当初始化泛型类的实例时,它会绑定到特定类型。由于你想在列表中保存不同类型的对象,因此必须创建一个绑定到最小公共分母的实例-在你的情况下是Object。

但是,这意味着Data属性现在将返回Object类型的对象。编译器无法在编译时推断实际数据类型,因此它可以选择适当的SomeMethod重载。

你必须提供一个以Object作为参数的SomeMethod重载,或者删除在集合中保存不同类型的要求。

或者你可以使用标准的IEnumerable集合(如Array)并使用OfType<>扩展方法来获取特定类型的子集合。


我担心这会导致使用对象…但是,由于SomeMethod来自库且我无法改变它不接受对象的事实,因此我无法使用对象…同时,我也不想对变量的类型进行switch()处理… - Stécy

1
您可以为SomeMethod创建一个通用的包装器,检查通用参数的类型,然后委托给适当的方法。
public void SomeMethod<T>(T value)
{
    Type type = typeof(T);

    if (type == typeof(int))
    {
        SomeMethod((int) (object) value); // sadly we must box it...
    }
    else if (type == typeof(float))
    {
        SomeMethod((float) (object) value);
    }
    else if (type == typeof(string))
    {
        SomeMethod((string) (object) value);
    }
    else
    {
        throw new NotSupportedException(
            "SomeMethod is not supported for objects of type " + type);
    }
}

谢谢你的回答。恐怕我最终还是得使用装箱/拆箱。我本以为一定有更有效率的方法。 - Stécy

1
在这种情况下,您需要使用 MyData<object>,因为这是这些类型唯一共有的东西。

2
MyData<object> 如何允许调用 SomeMethod(int)? - Amy B

1

之前建议使用通配符here。但被标记为“不予修复”:(


0
从非泛型的MyData类继承MyData<T>,并创建一个列表。
这样,你就不能自动解决重载。你必须手动解决。
abstract class MyData { 
   protected abstract object GetData();
   protected abstract Type GetDataType(); 
   public object Data {
      get { return GetData(); } 
   }
   public Type DataType {
      get { return GetDataType(); } 
   }
}

class MyData<T> : MyData { 
   protected override object GetData() { return Data; }
   protected override Type GetDataType() { return typeof(T); }
   public new T Data {
     get { ... }
   }
}

好的,现在我会像这样使用它:List<MyData> list = new List<MyData>(); list.Add (new MyData<int>()); list.Add (new MyData<string>());SomeMethod (list[0].Data);不知何故,我需要让 SomeMethod() 接受一个对象。但很遗憾,这并不是我所需要的。感谢您的回复。Stecy - Stécy
是的,这就是为什么我说你应该通过考虑类型来手动管理重载。泛型不支持直接的方式。 - Mehrdad Afshari

0

泛型允许您在创建列表时为整个列表指定一个类型,例如,用于存储 int 的列表将如下创建

var myData = new MyData<int>();

如果您想在同一个通用列表中存储多种类型,可以为这些类型指定一个共同的基础类型或接口。不幸的是,在您的情况下,您想要存储的类型的唯一共同基础类型将是Object。

var myData = new MyData<object>();

但是你可以使用非泛型列表来存储对象。


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