Ограничение параметров универсального типа для определенного конструктора

Я хотел бы знать, почему новое ограничение на параметр универсального типа может применяться только без параметров, то есть можно ограничить тип наличием конструктора без параметров, но нельзя ограничить класс иметь, скажем, конструктор, который получает int в качестве параметра. Я знаю способы обойти это, используя отражение или фабричный шаблон, который отлично работает, хорошо. Но мне очень хотелось бы знать, почему, потому что я думал об этом и действительно не могу придумать разницу между конструктором без параметров и конструктором с параметрами, которая оправдывала бы это ограничение для нового ограничения. Что мне не хватает? Большое спасибо


Аргумент 1: Конструкторы — это методы

@Eric: Позвольте мне пойти сюда с вами на секунду:

Конструкторы — это методы

Тогда, я думаю, никто не будет возражать, если я d поступаю следующим образом:

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) и новые(Кот,Автомобиль).

Что ж, давайте попробуем это (что, на мой взгляд, похоже на ситуацию, предложенную @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.Автомобиль)'

Я не понимаю, почему результат должен быть другим, если одна и та же проблема возникает из решения с параметризованными ограничениями конструктора, например:

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 gc = new GenericClass();аналогично полученной при попытке скомпилировать первый например, метод FunnyMethod.

В любом случае, я бы пошел еще дальше.Когда кто-то переопределяет абстрактный метод или реализует метод, определенный в интерфейсе, он должен делать это с точно таким же типом параметров, без разрешенных наследников или предков. Таким образом, когда требуется параметризованный конструктор, требование должно удовлетворяться точным определением, а не чем-либо еще. В этом случае класс FunnyClassникогда не может быть указан в качестве типа для универсального типа параметра класса GenericClass.

19
задан nawfal 16 July 2014 в 15:29
поделиться