Коллекция универсальных типов
Если у меня есть общий класс:
public class MyClass<T>
{
public T Value;
}
Я хочу создать несколько экземпляров, таких как...
new MyClass<string>
new MyClass<int>
... и добавить их в коллекцию. Как определить коллекцию, чтобы она могла содержать список универсальных типов? Затем я хочу перебрать коллекцию и использовать свойство Value. Возможный?
11 ответов
Пусть ваш универсальный класс наследует от неуниверсальной базы или реализует неуниверсальный интерфейс. Затем вы можете иметь коллекцию этого типа и использовать любой код, который вы используете для доступа к содержимому коллекции.
Вот пример.
public abstract class MyClass
{
public abstract Type Type { get; }
}
public class MyClass<T> : MyClass
{
public override Type Type
{
get { return typeof(T); }
}
public T Value { get; set; }
}
// VERY basic illustration of how you might construct a collection
// of MyClass<T> objects.
public class MyClassCollection
{
private Dictionary<Type, MyClass> _dictionary;
public MyClassCollection()
{
_dictionary = new Dictionary<Type, MyClass>();
}
public void Put<T>(MyClass<T> item)
{
_dictionary[typeof(T)] = item;
}
public MyClass<T> Get<T>()
{
return _dictionary[typeof(T)] as MyClass<T>;
}
}
Единственный способ, о котором я могу подумать, - это не думать о чем-то одном (завернуть в консольное приложение для тестирования):
class Program
{
static void Main(string[] args)
{
var x = new MyClass<string>() { Value = "34" };
var y = new MyClass<int>() { Value = 3 };
var list = new List<IMyClass>();
list.Add(x);
list.Add(y);
foreach (var item in list)
{
Console.WriteLine(item.GetValue);
}
}
private interface IMyClass
{
object GetValue { get; }
}
private class MyClass<T> : IMyClass
{
public T Value;
public object GetValue
{
get
{
return Value;
}
}
}
}
т.е. пусть MyClass реализует пустой интерфейс, а затем создает ваши коллекции в виде экземпляров классов, которые реализуют этот интерфейс.
Обновление: я добавил метод "GetValue" в интерфейс, который позволяет вам получить доступ к "значению" экземпляра MyClass как объекта. Кстати, это почти так же хорошо, как и то, что вы хотите иметь коллекцию, которая содержит смешанные типы.
Вы захотите определить базовый класс для MyClass, тогда ваши коллекции станут списком базового класса. Пример:
void Main()
{
var a = new MyClass<string>();
var b = new MyClass<int>();
var c = new List<MyBase>();
c.Add(a);
c.Add(b);
}
public class MyBase { }
// Define other methods and classes here
public class MyClass<T> : MyBase {
public T Value { get; set;}
}
У меня есть интерфейсы на большинстве моих универсальных типов с "нетипизированными" членами:
private interface IMyClass
{
object UntypedValue { get; }
}
private class MyClass<T> : IMyClass
{
T Value { get; set; }
object UntypedValue { get { return Value; } }
}
Вы также можете сделать это, используя явную реализацию интерфейса, но, на мой взгляд, это гораздо проще, если использовать отдельное имя. (Есть несколько советов CA о явной реализации интерфейса)
Вы хотите иметь коллекцию MyClass, для которой значение параметра типа T отличается в каждом экземпляре. Это невозможно в.NET; ему не хватает эквивалента подстановочного знака Java (?). Вместо этого вам нужно создать неуниверсальный базовый класс или интерфейс, который может реализовать MyClass. Например:
public interface IMyClass {
object Value { get; set; }
}
public class MyClass<T> : IMyClass {
public T Value { get; set; }
object IMyClass.Value {
get { return Value; }
set { Value = (T)value; }
}
}
List<IMyClass> m = new List<IMyClass> { new MyClass<string>(), new MyClass<int> };
Я думаю, что проблема в том, что универсальные классы на самом деле совсем не одного типа. Это просто шаблоны, которые создают совершенно новые типы во время компиляции (если я правильно понимаю). Следовательно, MyClass<int>
а также MyClass<string>
совершенно разные типы, в зависимости от времени выполнения. Они также могут быть MyIntClass
а также MyStringClass
, который вы, очевидно, не можете иметь в том же списке, не поместив их в бокс. Они не (обязательно) наследуют один и тот же базовый класс, реализуют те же интерфейсы или что-то еще. Они такие же разные, как и любые другие два типа, и вы должны относиться к ним как таковым (даже если вы думаете, что знаете лучше).
Конечно, вы можете сделать так, чтобы они реализовали интерфейс, унаследовали базовый объект или любые другие опции, которые уже были предоставлены. Посмотрите на ответ commongenius для хорошего способа сделать это.
Начиная с.Net 3 существует класс CompositeCollection, который позволяет содержать несколько уникальных элементов или даже коллекций. Он используется разработчиками WPF для хранения и отображения различных элементов в Xaml. Но это не значит, что его нельзя использовать в ситуациях, не связанных с WPF.
Вот пример, где я храню разные вещи от строк до десятичных знаков и извлекаю и перечисляю все элементы, а затем элементы определенного типа:
CompositeCollection cc = new CompositeCollection();
cc.Add(1);
cc.Add("Item One");
cc.Add(1.0M);
cc.Add(1.0);
cc.Add("Done");
Console.WriteLine ("Every Item");
foreach (var element in cc)
Console.WriteLine ("\t" + element.ToString());
Console.WriteLine (Environment.NewLine + "Just Strings");
foreach (var str in cc.OfType<string>())
Console.WriteLine ("\t" + str);
/* Outputs:
Every Item
1
Item One
1.0
1
Done
Just Strings
Item One
Done
*/
Я считаю, что ваша коллекция должна быть одного и того же типа MyClass (как в T должна быть одинаковой), потому что компилятор не будет знать, какие типы вы добавили к каким элементам в коллекции.
Другими словами, если бы вы добавили 2 элемента в список:
list.Add(new MyClass<string>());
list.Add(new MyClass<int>());
затем попробуйте сослаться на один:
var myItem = list[1];
Компилятор не знает, какой универсальный объект был назначен списку MyClass в элементе 1, потому что элементы добавляются во время выполнения, но универсальные элементы определяются во время компиляции.
Я уверен, что то, что вы хотите сделать, не может быть сделано.
Если вы заранее знаете количество элементов, возможно, вы могли бы использовать Tuple?
IList
Нет никакого способа определить универсальную коллекцию, которая может принять любой тип вашего универсального класса... т.е. IList<MyClass>
, Универсальные классы - это всего лишь краткий путь для разработчика, чтобы сэкономить на написании связки повторяющегося кода, но во время компиляции каждый вариант универсального класса переводится в конкретный. т.е. если у вас есть MyClass<string>
, MyClass<int>
, MyClass<bool>
тогда компилятор сгенерирует 3 отдельных и разных класса. Единственный способ сделать то, что вы хотите, это иметь интерфейс для вашего универсального.
public interface IMyGeneric {
Type MyType { get; set;}
}
class MyGeneric<T> : IMyGeneric {
public MyGeneric() {
MyType = typeof(T);
}
public Type MyType {
get; set;
}
}
и тогда вы можете сказать
IList<IMyGeneric> list = new List<IMyGeneric>();
list.add(new MyGeneric<string>());
list.add(new MyGeneric<int>());
Другой подход - использовать неуниверсальный класс и передать ему обработчик для выполнения действия «изнутри».
interface IMyClass
{
void ShowValues(MyHandler handler);
}
// Contains a List<string>.
class MyClassString : IMyClass
{
private List<string> _list = new List<string> { "hello", "world" };
public void ShowValues(MyHandler handler)
{
handler.ShowValues(_list);
}
}
// Contains a List<int>.
class MyClassInt : IMyClass
{
private List<int> _list = new List<int> { 1, 2 };
public void ShowValues(MyHandler handler)
{
handler.ShowValues(_list);
}
}
// Handler that has overloaded methods for all the supported types.
class MyHandler
{
public void ShowValues(IEnumerable<string> list)
{
foreach (var item in list)
{
Console.WriteLine(item);
}
}
public void ShowValues(IEnumerable<int> list)
{
foreach (var item in list)
{
Console.WriteLine(item);
}
}
}
class Program
{
static void Main(string[] args)
{
var myClasses = new IMyClass[]
{
new MyClassString(),
new MyClassInt(),
};
var myHandler = new MyHandler();
foreach (var myClass in myClasses)
{
myClass.ShowValues(myHandler);
}
}
}
Извините, я опоздал на вечеринку, но мне нужны очки. Уловка всего этого процесса состоит в том, чтобы думать о типах «int» и «string» как о классах. Таким образом, уловка состоит в том, чтобы иметь список <MyClass <object>>.
Теперь вы спрашиваете, как мне заставить класс создавать объекты любого использования или обеспечивать проверку типов для моих входных данных для объекта? Вы не можете. Потому что объект - это тупой элемент базового уровня, и компилятор действительно мало что может с ним поделать. Однако решение вашей проблемы основано на той же идее.
Что, если бы вы создали классы MyInt и MyString вместо классов int и string? Что ж, теперь, аналогично int и string, вы можете расширить эти классы для реализации общего интерфейса, такого как IMyClassType. Обновите MyClass до открытого класса MyClass <T>, где T:IMyClassType. Теперь замените экземпляры MyClass <int> на Myclass <MyInt> и MyClass <MyString>.
Наконец, после всего этого вы можете заменить бесполезный List<MyClass<object>> на полезный List<MyClass<IMyClassType>>, который может содержать экземпляры MyClass, содержащие свойства и методы, ссылающиеся на созданные вами IMyClassTypes.
Это требование C# к дизайну связано с шаблоном, который называется Generic Adapter Pattern , и как только вы его получите, он упростит разработку сложных объектов с взаимозависимыми типами объектов.