Я хотел бы знать, почему новое ограничение на параметр универсального типа может применяться только без параметров, то есть можно ограничить тип, чтобы иметь конструктор без параметров, но нельзя ограничить класс, скажем, конструктор, который получает int в качестве параметра. Я знаю способы обойти это, используя отражение или фабричный шаблон, который отлично работает, хорошо. Но я действительно хотел бы знать почему, потому что я думал об этом, и я действительно не могу думать о разнице между конструктором без параметров и конструктором с параметрами, которые бы оправдывали это ограничение для нового ограничения. Что мне не хватает?
Большое спасибо
Аргумент 1: конструкторы методы
@ Эрик: Позвольте мне побыть здесь с вами на секунду:
Конструкторы - это методы
Тогда я полагаю, что никто не будет возражать, если я пойду так:
public interface IReallyWonderful
{
new(int a);
string WonderMethod(int a);
}
Но как только я получу это, я пойду:
public class MyClass<T>
where T : IReallyWonderful
{
public string MyMethod(int a, int b)
{
T myT = new T(a);
return myT.WonderMethod(b);
}
}
Именно это я и хотел сделать в первую очередь. Итак, извините, но нет, конструкторы не являются методами или, по крайней мере, не совсем.
О трудностях реализации этой функции, ну, я бы на самом деле не знал, и даже если бы я знал, мне нечего было бы сказать по поводу решения относительно разумного расходования денег акционеров. Нечто подобное я бы сразу отметил как ответ.
С академической (моей) точки зрения, то есть без учета затрат на внедрение, вопрос действительно (я округлил это до последних нескольких часов):
Должны ли конструкторы рассматриваться как часть реализации класса или как часть семантического контракта (так же, как интерфейс считается семантическим контрактом).
Если мы рассматриваем конструкторы как часть реализации, то ограничение конструктора универсального параметра типа - не очень универсальная вещь, так как это связывает ваш универсальный тип с конкретной реализацией, а один почти мог бы сказать зачем вообще использовать дженерики?
Пример конструктора как части реализации (нет смысла указывать любой из следующих конструкторов как часть семантического контракта, определенного ITransformer
):
public interface ITransformer
{
//Operates with a and returns the result;
int Transform(int a);
}
public class PlusOneTransformer : ITransformer
{
public int Transform(int a)
{
return a + 1;
}
}
public class MultiplyTransformer : ITransformer
{
private int multiplier;
public MultiplyTransformer(int multiplier)
{
this.multiplier = multiplier;
}
public int Transform(int a)
{
return a * multiplier;
}
}
public class CompoundTransformer : ITransformer
{
private ITransformer firstTransformer;
private ITransformer secondTransformer;
public CompoundTransformer(ITransformer first, ITransformer second)
{
this.firstTransformer = first;
this.secondTransformer = second;
}
public int Transform(int a)
{
return secondTransformer.Transform(firstTransformer.Transform(a));
}
}
Проблема в том, что конструкторы также могут рассматриваться как часть семантического контракта, например:
public interface ICollection<T> : IEnumerable<T>
{
new(IEnumerable<T> tees);
void Add(T tee);
...
}
Это означает, что всегда возможно собрать коллекцию из последовательности элементов, верно? И это сделало бы очень правильную часть семантического контракта, верно?
Я, без учета каких-либо аспектов разумного расходования средств акционеров, предпочел бы позволить конструкторам участвовать в семантических контрактах. Некоторые разработчики портят это и ограничивают определенный тип наличием семантически некорректного конструктора. Ну, в чем же отличие от того же разработчика, добавляющего семантически некорректную операцию? В конце концов, семантические контракты таковы, потому что мы все согласны с этим, и потому что мы все хорошо документируем наши библиотеки;)
Аргумент 2: предполагаемые проблемы при разрешении конструкторов
@ supercat пытается привести несколько примеров того, как (цитата из комментария)
Было бы также трудно точно определить, как должны работать ограничения конструктора, не приводя к неожиданному поведению.
но я действительно должен не согласиться. В C # (ну, в .NET) такие сюрпризы, как «Как заставить пингвина летать?» просто не бывает Существуют довольно простые правила относительно того, как компилятор разрешает вызовы методов, и если компилятор не может разрешить это, ну, он не пройдет, не скомпилирует это.
Его последний пример был:
Если они противоречивы, то возникает проблема с разрешением того, какой конструктор следует вызывать, если универсальный тип имеет ограничение new (Cat, ToyotaTercel), а у фактического типа просто есть конструкторы new (Animal, ToyotaTercel) и new(Cat, Automobile).
Хорошо, давайте попробуем это (что, по моему мнению, похоже на ситуацию, предложенную @supercat)
class Program
{
static void Main(string[] args)
{
Cat cat = new Cat();
ToyotaTercel toyota = new ToyotaTercel();
FunnyMethod(cat, toyota);
}
public static void FunnyMethod(Animal animal, ToyotaTercel toyota)
{
Console.WriteLine("Takes an Animal and a ToyotaTercel");
}
public static void FunnyMethod(Cat cat, Automobile car)
{
Console.WriteLine("Takes a Cat and an Automobile");
}
}
public class Automobile
{ }
public class ToyotaTercel : Automobile
{ }
public class Animal
{ }
public class Cat : Animal
{ }
И,вау, он не скомпилируется с ошибкой
Вызов неоднозначен между следующими методами или свойствами: 'TestApp.Program.FunnyMethod (TestApp.Animal, TestApp.ToyotaTercel)' и 'TestApp.Program.FunnyMethod (TestApp.Cat, TestApp.Automobile) '
Я не понимаю, почему результат должен отличаться, если та же проблема возникает из решения с параметризованными ограничениями конструктора, например:
class Program
{
static void Main(string[] args)
{
GenericClass<FunnyClass> gc = new GenericClass<FunnyClass>();
}
}
public class Automobile
{ }
public class ToyotaTercel : Automobile
{ }
public class Animal
{ }
public class Cat : Animal
{ }
public class FunnyClass
{
public FunnyClass(Animal animal, ToyotaTercel toyota)
{
}
public FunnyClass(Cat cat, Automobile car)
{
}
}
public class GenericClass<T>
where T: new(Cat, ToyotaTercel)
{ }
Теперь, конечно, компилятор не может обработать ограничение на конструкторе, но если это возможно, то почему ошибка не может быть в строке GenericClass<FunnyClass> gc = new GenericClass<FunnyClass>();
, аналогичной tТаким образом, при попытке скомпилировать первый пример, пример из FunnyMethod
.
В любом случае, я бы пошел еще дальше.Когда кто-то переопределяет абстрактный метод или реализует метод, определенный в интерфейсе, он должен делать это с точно таким же типом параметров, наследники или предки не допускаются.Таким образом, когда требуется параметризованный конструктор, это требование должно выполняться с точным определением, а не с чем-либо еще.В этом случае класс FunnyClass
никогда не может быть указан в качестве типа для универсального типа параметра класса GenericClass
.