С# общий шаблон или как сохранить ссылку на неизвестное общее наследство

ОК, так вот ситуация. У меня есть класс 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()
    {
    }