С# общий шаблон или как сохранить ссылку на неизвестное общее наследство
ОК, так вот ситуация. У меня есть класс FlexCollection<T>
, целью которого является сохранение списка некоторой специализации FlexItem
, поэтому:
public class FlexCollection<T> where T : FlexItem, new()
{
public void Add(T item) { ... }
...
}
FlexItem
не является общим классом. То, что я хотел достичь, - это способность удерживать в поле FlexItem
ссылку на коллекцию, содержащую объект. К сожалению, в С# невозможно провести ссылку на "любую" специализацию класса шаблона (как в Java). Сначала я попытался использовать не-общий интерфейс IFlexCollection
, но он фактически заставил меня реализовать каждый метод дважды, то есть:
public class FlexCollection<T> : IFlexCollection where T : FlexItem, new()
{
public void Add(T item) { ... } // to use in generic calls
public void Add(object item) { ... } // to use for calls from within FlexItem
...
}
Тогда я узнал, что могу сделать FlexItem самим универсальным классом! Тогда специализация может содержать ссылку на сбор объектов этой специализации (что вполне логично). Поэтому:
public class FlexItem<T> where T : FlexItem<T>, new()
{
public FlexCollection<T> ReferenceToParentCollection;
...
}
public class FlexCollection<T> where T : FlexItem<T>, new()
{
public void Add(T item) { ... }
...
}
Теперь я могу объявить некоторую специализацию FlexItem<T>
и соответствующую коллекцию:
public class BasicItem : FlexItem<BasicItem> { public int A; }
public class BasicCollection : FlexCollection<BasicItem> { };
Проблема возникает, когда я пытаюсь расширить эти классы для хранения дополнительных полей. То есть Мне нужен класс ExtendedItem
, который содержит поле B
в дополнение к полю A
:
public class ExtendedItem : BasicItem { public int B; }
public class ExtendedCollection : FlexCollection<ExtendedItem> { };
И дело в том, что ExtendedItem
является подклассом FlexItem<BasicItem>
, а не FlexItem<ExtendedItem>
. Поэтому невозможно объявить ExtendedCollection
, как указано выше. Это вызывает ошибку компиляции:
The type 'Demo.ExtendedItem' must be convertible to
'Demo.FlexItem<Demo.ExtendedItem>' in order to use it as parameter 'T'
in the generic type or method 'Demo.BasicCollection<T>'
Есть ли способ избежать такого столкновения типа?
Ответы
Ответ 1
ОК, я решил попробовать С# события. Таким образом, на самом деле мне не нужен FlexItem для ссылки на владение FlexCollection. Я просто поднимаю событие, то есть "IAmBeingRemoved" и код FlexCollection, если вы выполняете соответствующие действия. Тогда логика кода еще лучше инкапсулирована. Я просто надеюсь, что у меня не возникнут проблемы с производительностью или другие обобщенные сюрпризы.:-) Я отправляю его так, чтобы, возможно, кто-то нашел это хорошим решением для своей собственной проблемы.
Ответ 2
Есть два способа решить эту проблему:
Вы можете использовать базовую Полиморфную коллекцию и не наследовать коллекцию базового класса:
class Program
{
static void Main(string[] args)
{
BasicCollection extendedCollection = new BasicCollection();
extendedCollection.Add(new ExtendedItem { A = 1, B = 2});
extendedCollection.Add(new BasicItem { A = 3 });
extendedCollection.Add(new ExtendedItem { A = 4, B = 5});
foreach (BasicItem item in extendedCollection)
{
switch(item.GetType().Name)
{
case "BasicItem":
Console.Out.WriteLine(string.Format("Found BasicItem: A={0}", item.A));
break;
case "ExtendedItem":
ExtendedItem extendedItem = item as ExtendedItem;
Console.Out.WriteLine(string.Format("Found ExtendedItem: A={0} B={1}", extendedItem.A, extendedItem.B));
break;
}
}
}
}
public class FlexItem<T> where T : FlexItem<T>, new()
{
public FlexCollection<BasicItem> ReferenceToParentCollection;
}
public class FlexCollection<T> where T : FlexItem<T>, new()
{
public void Add(T item) { }
}
public class BasicItem : FlexItem<BasicItem> { public int A; }
public class ExtendedItem : BasicItem { public int B; }
public class BasicCollection : FlexCollection<BasicItem>
{
Collection<BasicItem> items = new Collection<BasicItem>();
public void Add(BasicItem item)
{
item.ReferenceToParentCollection = this;
items.Add(item);
}
public void Remove(BasicItem item)
{
item.ReferenceToParentCollection = null;
items.Remove(item);
}
public IEnumerator GetEnumerator()
{
return items.GetEnumerator();
}
}
Или вы можете вставить ссылку класса коллекции и удалить ее, когда вам это нужно, так как вы знаете тип дочернего объекта?
class Program
{
static void Main(string[] args)
{
ExtendedCollection extendedCollection = new ExtendedCollection();
extendedCollection.Add(new ExtendedItem { A = 1, B = 2, ReferenceToParentCollection = extendedCollection });
extendedCollection.Add(new ExtendedItem { A = 3, B = 3, ReferenceToParentCollection = extendedCollection });
foreach (ExtendedItem item in extendedCollection)
{
(item.ReferenceToParentCollection as ExtendedCollection) ...
}
}
}
public class FlexItem<T> where T : FlexItem<T>, new()
{
public object ReferenceToParentCollection;
}
public class FlexCollection<T> where T : FlexItem<T>, new()
{
public void Add(T item) { }
}
public class BasicItem : FlexItem<BasicItem> { public int A; }
public class BasicCollection : FlexCollection<BasicItem> { };
public class ExtendedItem : BasicItem { public int B; }
public class ExtendedCollection : FlexCollection<ExtendedItem> { };
Ответ 3
Не уверен, в чем цель (использование) вашего кода, но мне кажется, что вы пытаетесь что-то основать на универсальном решении, а затем "избавляетесь" от общих данных о некоторых типах - это не возможно в С#.
Если вы решите использовать общий подход, вы всегда должны хранить информацию о базовых типах:
public class FlexItem<T> where T : FlexItem<T>, new()
{
public FlexCollection<T> ReferenceToParentCollection;
}
public class FlexCollection<T> where T : FlexItem<T>, new()
{
public void Add(T item)
{
}
}
public class BasicItem<T> : FlexItem<BasicItem<T>>
{
public int A;
}
public class BasicCollection<T> : FlexCollection<BasicItem<T>>
{
}
public class ExtendedItem<T> : BasicItem<T>
{
public int B;
}
public class ExtendedCollection<T> : FlexCollection<T> where T: FlexItem<T>, new()
{
}