Как добавить универсальное ограничение к конструктору не-универсального класса в C#

#c# #.net #oop #generics

#c# #.net #ооп #дженерики

Вопрос:

Как следует из названия, я хотел бы сделать следующее, но компилятор не позволяет мне:

 public class MyClass
{
    private List<SomeSupertype> myList;

    public MyClass<T> (ICollection<T> elements) where T : SomeSupertype
    {
        myList = new List<SomeSuperType> ();
        foreach (SomeSupertype element in elements) {
            myList.Add (element);
        }
    }
}
  

Почему-то кажется невозможным добавить универсальный параметр в конструктор класса, который сам по себе не является универсальным. Обратите внимание, что MyClass должен быть не-универсальным типом, потому что клиенту не нужно различать разные типы MyClass после его создания.

Кто-нибудь может помочь мне разобраться в этом? Возможно ли это вообще? Это, безусловно, есть в Java 🙂

Редактировать: я забыл упомянуть, что я использую .NET 3.5 и, следовательно, нет поддержки ковариантных обобщений.

Комментарии:

1. Если вы используете .NET 4, почему бы просто не использовать неродовой конструктор IEnumerable<SomeSupertype> ? IEnumerable 1` является ковариантным, поэтому IEnumerable<T> преобразуется в IEnumerable<SomeSupertype> любое T : SomeSupertype время.

2. @Anton: С некоторыми изменениями и объяснениями это кажется достойным ответа

3. Да, ковариация решила бы проблему. Вероятно, мне следует упомянуть, что я использую .NET 3.5

Ответ №1:

Конструкторы в C # не могут быть универсальными (то же самое с событиями, свойствами, деструкторами, операторами). Вместо этого рассмотрите возможность создания статического универсального фабричного метода:

 public class MyClass
{
    private List<SomeSupertype> myList;

    private MyClass(List<SomeSupertype> myList)
    {
        this.myList = myList;
    }

    public static MyClass Create<T>(ICollection<T> elements)
        where T : SomeSupertype
    {
        var myList = new List<SomeSuperType>(elements);
        foreach (SomeSupertype element in elements)
        {
            myList.Add(element);
        }
        return new MyClass(myList);
    }
}
  

Кстати, даже в .NET 3.5 вы могли бы использовать LINQ, чтобы упростить создание списка:

 var myList = elements.Cast<SomeSuperType>().ToList();
  

Ответ №2:

Вы могли бы использовать шаблон factory для решения вашей проблемы:

 public class MyClass
{
    private List<SomeSuperType> myList = new List<SomeSuperType>();

    public static MyClass MyClassBuilder<T>(ICollection<T> elements) where T : SomeSuperType
    {
        var myClass = new MyClass();

        foreach (SomeSuperType element in elements)
        {
            myClass.myList.Add(element);
        }

        return myClass;
    }

    protected MyClass()
    {
    }
}
  

Или вы могли бы создать два класса, например:

 // Put all the methods here
public class MyClass
{
    protected List<SomeSuperType> myList = new List<SomeSuperType>();

    protected MyClass()
    {
    }
}

// This class will define only the generic constructor
public class MyClass<T> : MyClass where T : SomeSuperType
{
    public MyClass(ICollection<T> elements)
    {
        foreach (SomeSuperType element in elements)
        {
            myList.Add(element);
        }
    }
}
  

(это игнорирует тот факт, что, как написано Anton , в C # > = 4.0 вы могли бы просто принять IEnumerable<SomeSuperType> и быть счастливым)

Ответ №3:

Вы не можете сделать это в C #. Это хорошо, поскольку нам не нужны конструкторы, которые не соответствуют классу. Но все же есть несколько способов, которыми вы можете достичь своей цели.

Самый простой способ (если вы используете C # 4) — использовать IEnumerable вместо ICollection, потому что IEnumerable поддерживает контравариантность.

 public class MyClass
{
    private List<SomeSupertype> myList;

    public MyClass (IEnumerable<SomeSuperType> elements)
    {
        myList = new List<SomeSuperType> ();
        foreach (SomeSupertype element in elements) {
            myList.Add (element);
        }
    }
}
  

Теперь вы можете вызывать constructor, не думая о приведении коллекции производных типов к коллекции базовых типов.

 List<SomeDerivedClass> col = new List<SomeDerivedType>();
MyClass obj1 = new MyClass(col)
  

Второе решение, вы могли бы просто вызвать свой конструктор, создав коллекцию с помощью LINQ.

 MyClass obj1 = new MyClass(col.Cast<SomeSuperClass>());
  

Третье решение, вы могли бы создать статический универсальный метод для создания новых объектов вашего класса.

 public static MyClass Construct<T>(ICollection<T> elements) where T : ClassA
{
    return new MyClass(elements.Cast<ClassA>().ToArray());
}
  

Четвертое решение, вы могли бы создать универсальный класс, который является производным от вашего базового класса, как кто-то уже указал.