Я хотел бы знать, почему новое ограничение для параметра общего типа может применяться только без параметров, т.е. можно ограничить тип конструктором без параметров, но нельзя ограничить класс, чтобы, скажем, конструктор, который получает int как параметр. Я знаю об этом, используя рефлексию или шаблон factory, который работает нормально, хорошо. Но я действительно хотел бы знать, почему, потому что я думал об этом, и я действительно не могу представить разницу между конструктором без параметров и параметрами, которые оправдывали бы это ограничение на новом ограничении. Что мне не хватает? Большое спасибо
Аргумент 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 пытается указать некоторые примеры как (цитата из комментария)
Также было бы трудно точно определить, как должны работать конструкторские ограничения, не приводя к неожиданному поведению.
но я действительно должен не согласиться. В С# (ну, в .NET) удивляет, как "Как сделать пингвиновую муху?" просто не бывает. Есть довольно простые правила относительно того, как компилятор разрешает вызовы методов, и если компилятор не может его решить, ну, он не пройдет, не будет компилироваться.
Последний пример:
Если они контравариантны, тогда возникает проблема, решающая, какой конструктор следует вызывать, если общий тип имеет ограничение new (Cat, ToyotaTercel), а у фактического типа есть только конструкторы new (Animal, ToyotaTercel) и new (Cat, Автомобили).
Ну, давайте попробуем это (что, на мой взгляд, похоже на ситуацию, предложенную @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>();
, подобной полученной при попытке скомпилировать первый пример, а <<28 > .
Во всяком случае, я бы сделал еще один шаг. Когда кто-то переопределяет абстрактный метод или реализует метод, определенный на интерфейсе, требуется сделать это с точно таким же типом параметров, что и наследники или предки не разрешены. Поэтому, когда требуется параметризованный конструктор, требование должно выполняться с точным определением, а не с чем-либо еще. В этом случае класс FunnyClass
никогда не может быть указан как тип для общего типа параметров класса GenericClass
.