C#: как объявить и использовать список универсальных классов с разными типами?

Имея следующий универсальный класс, который будет содержать либо string, int, float, longв качестве типа:

public class MyData<T>
{
    private T _data;

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

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

Я пытаюсь получить список, в MyData<T>котором каждый элемент будет отличаться T.

Я хочу получить доступ к элементу из списка и получить его значение, как показано в следующем коде:

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)не существует.


Однако я не могу найти способ порадовать компилятор.

Какие-либо предложения?

Спасибо.

Ответов (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);
    }
}

Просто используйте ArrayList и забудьте о MyData<T> типе.

ArrayList myStuff = getStuff();
float x = myStuff.OfType<float>().First();
SomeMethod(x);
string s = myStuff.OfType<string>().First();
SomeMethod(s);

Проблема в MyData<T> том, что вы ожидаете, что компилятор проверит тип, который известен только во время выполнения. Компиляторы проверяют типы, известные во время компиляции.

Generics позволяют вам указать один тип для всего списка при создании списка, например, список для хранения int будет создан следующим образом

var myData = new MyData<int>();

Если вы хотите сохранить несколько типов в одном общем списке, вы можете указать общий базовый тип или интерфейс для этих типов. К сожалению, в вашем случае единственным распространенным базовым типом для типов, которые вы хотите сохранить, будет object.

var myData = new MyData<object>();

Но вы можете просто использовать неуниверсальный список для хранения объектов.

В этом случае вам нужно, MyData<object> поскольку это единственное, что у этих типов общего.

Предлагаемые подстановочные знаки некоторое время назад здесь . Закрыто как "не исправлю" :(

Вы не можете делать это так, как хотите.

Когда экземпляр универсального класса инициализируется, он привязывается к определенному типу. Поскольку вы хотите хранить в своем списке объекты разных типов, вам нужно создать экземпляр, привязанный к наименьшему общему знаменателю - в вашем случае это Object.

Однако это означает, что свойство Data теперь будет возвращать объект типа Object. Компилятор не может определить фактический тип данных во время компиляции, поэтому он может выбрать соответствующую SomeMethod перегрузку.

Вы должны либо предоставить перегрузку, SomeMethod которая принимает Object в качестве параметра, либо удалить требование хранить различные такие разные типы в вашей коллекции.

Или вы можете использовать стандартную IEnumerable коллекцию (например, Array) и использовать OfType<> метод расширения, чтобы получить подмножество коллекции определенного типа.

Наследуйте MyData<T> неуниверсальный MyData класс и составьте его список.

Таким образом, вы не можете автоматически устранить перегрузку. Это нужно делать вручную.

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 { ... }
   }
}

Я думаю, что проблема в том, что вы пытаетесь создать общий тип, а затем создать список этого универсального типа. Вы можете выполнить то, что пытаетесь сделать, заключив контракт с типами данных, которые вы пытаетесь поддерживать, например, как элемент IData, а затем создать свой общий MyData с ограничением IData. Обратной стороной этого будет то, что вам придется создавать свои собственные типы данных для представления всех используемых вами примитивных типов данных (string, int, float, long). Это может выглядеть примерно так:

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 и проверить тип универсального аргумента, а затем передать полномочия соответствующему методу.

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);
    }
}