С# generics: упростить подпись типа

Если у меня есть общий класс Item, который выглядит так:

abstract class Item<T>
{
}

И контейнер элементов, который выглядит следующим образом:

class Container<TItem, T>
    where TItem : Item<T>
{
}

Так как TItem зависит от T, возможно ли упростить сигнатуру типа Container так, что он принимает только один параметр типа? Я действительно хочу что-то такое:

class Container<TItem>
    where TItem : Item   // this doesn't actually work, because Item takes a type parameter
{
}

Поэтому я могу создать его следующим образом:

class StringItem : Item<string>
{
}

var good = new Container<StringItem>();
var bad = new Container<StringItem, string>();

Компилятор должен уметь выводить, что T является строкой, когда TItem является StringItem, правильно? Как это сделать?

Желаемое использование:

class MyItem : Item<string>
{
}

Container<MyItem> container = GetContainer();
MyItem item = container.GetItem(0);
item.MyMethod();

Ответы

Ответ 1

Это должно делать то, что вы хотите, я думаю. Очевидно, что теперь вы выполняете Container<string> not Container<StringItem>, но поскольку вы не включили примеры использования, я не вижу в этом проблемы.

using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var myContainer = new Container<string>();

            myContainer.MyItems = new List<Item<string>>();
        }
    }

    public class Item<T> { }

    public class Container<T>
    {
        // Just some property on your container to show you can use Item<T>
        public List<Item<T>> MyItems { get; set; }
    }
}

Как насчет этой измененной версии:

using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var myContainer = new Container<StringItem>();

            myContainer.StronglyTypedItem = new StringItem();
        }
    }

    public class Item<T> { }

    public class StringItem : Item<string> { }

    // Probably a way to hide this, but can't figure it out now
    // (needs to be public because it a base type)
    // Probably involves making a container (or 3rd class??)
    // wrap a private container, not inherit it
    public class PrivateContainer<TItem, T> where TItem : Item<T> { }

    // Public interface
    public class Container<T> : PrivateContainer<Item<T>, T>
    {
        // Just some property on your container to show you can use Item<T>
        public T StronglyTypedItem { get; set; }
    }
}

Ответ 2

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

interface IItem { }

abstract class Item<T> : IItem { }

class Container<TItem> where TItem : IItem { }

class StringItem: Item<string> { }

И теперь вы можете иметь Container<StringItem>:

var container = new Container<StringItem>();