Поддерживается ли универсальный конструктор в неродовом классе?

#c# #generics #constructor

#c# #общие #конструктор

Вопрос:

Это не поддерживается, поддерживается ли, но я должен выполнить некоторые трюки?

Пример:

 class Foo
{
  public Foo<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2)
  {
     ...
  }
}
  

общие элементы используются только в конструкторе, от них не зависит поле / свойство, я использую его (общие элементы) для обеспечения соответствия типов для f1 и f2.

Замечание: Я нашел обходной путь — статический метод Create, но в любом случае мне любопытно, почему у меня проблема с простым подходом.

Ответ №1:

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

Просто иногда я согласен, что это было бы удобно, но синтаксис выглядел бы довольно ужасно. Например, предположим, у вас был:

 public class Foo<T> {}

public class Foo
{
    public Foo<T>() {}
}
  

Что бы

 new Foo<string>()
  

поддерживается ли? Вызовите универсальный конструктор неродового класса или обычный конструктор универсального класса? Вам пришлось бы как-то различать их, и это было бы грязно : (

Аналогично, рассмотрим универсальный конструктор в родовом классе:

 public class Foo<TClass>
{
    public Foo<TConstructor>() {}
}
  

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

 new Foo<string><int>()
  

довольно отвратительно…

Так что да, семантически это было бы иногда полезно — но, к сожалению, получающееся уродство уравновешивает это.

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

1. Вы могли бы обойти проблему с одноименным классом, не разрешая общий и неродовой классы с одинаковым именем (серьезно, позволяет ли C # это?). Что касается универсального конструктора универсального класса, я не думаю, что это слишком отвратительно — это просто универсальность более высокого порядка.

2. Одно замечание — конструктор в универсальном классе является универсальным, потому что класс является универсальным. Однако (принимая во внимание ваш ответ) он не может указывать дополнительные универсальные аргументы. Спасибо за очень хорошие примеры!

3. @Peter: Нет, проблема с классом с одинаковым именем не является проблемой, потому что, хотя вы можете «перегружать» классы по типам, двусмысленности нет. Смотрите Tuple пример этого.

4. @macias: Я не считаю конструктор универсальным именно потому , что он не вводит никаких дополнительных параметров типа. Считаете ли вы List<T>.IndexOf универсальным методом только потому, что он относится к универсальному типу?

5. Питер — на самом деле перегрузка имени класса полезна, потому что вы помещаете в неродовой класс все статические методы. В C у меня всегда есть такие нечетные пары MyClass<…> и MyClassFactory. В C # у вас может быть несколько классов кортежей и кортеж вызова. Create(5, «hello»,4.5) является «чистым».

Ответ №2:

Универсальные конструкторы не поддерживаются, но вы можете обойти это, просто определив универсальный, static метод, который возвращает новый Foo :

 class Foo
{
  public static Foo CreateFromFuncs<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2)
  {
     ...
  }
}
  

который используется следующим образом:

 // create generic dependencies
var func1 = new Func<byte, string>(...);
var func2 = new Func<string, byte>(...);

// create nongeneric Foo from dependencies
Foo myFoo = Foo.CreateFromFuncs<byte, string>(func1, func2);
  

Ответ №3:

Вот практический пример того, как вы хотели бы иметь дополнительный параметр типа конструктора, и обходной путь.

Я собираюсь представить простую RefCounted оболочку для IDisposable :

 public class RefCounted<T> where T : IDisposable
{
    public RefCounted(T value)
    {
        innerValue = value;
        refCount = 1;
    }

    public void AddRef()
    {
        Interlocked.Increment(ref refCount);
    }

    public void Dispose()
    {
        if(InterlockedDecrement(ref refCount)<=0)
            innerValue.Dispose();
    }

    private int refCount;
    private readonly innerValue;
}
  

Кажется, это нормально. Но рано или поздно вы захотите привести a RefCounted<Control> к RefCounted<Button> , сохраняя при этом подсчет ссылок на оба объекта, т. Е. только когда оба экземпляра будут удалены для удаления базового объекта.

Лучший способ — это если бы вы могли написать (как это могут делать люди на C )

 public RefCounted(RefCounted<U> other)
{
    ...whatever...
}
  

Но C # этого не допускает. Итак, решение заключается в использовании некоторой косвенности.

 private readonly Func<T> valueProvider;
private readonly Action disposer;

private RefCounted(Func<T> value_provider, Action disposer)
{
    this.valueProvider = value_provider;
    this.disposer = disposer;
}

public RefCounted(T value) : this(() => value, value.Dispose)
{
}

public RefCounted<U> Cast<U>() where U : T 
{
    AddRef();
    return new RefCounted<U>(() => (U)(valueProvider()),this.Dispose);
}

public void Dispose(){
    if(InterlockedDecrement(ref refCount)<=0)
        disposer();
}
  

Если в вашем классе есть какие-либо поля универсального типа, у вас нет выбора, кроме как поместить все эти типы в класс. Однако, если вы просто хотели скрыть какой-то тип от конструктора, вам нужно будет использовать вышеупомянутый трюк — наличие скрытого конструктора для объединения всего вместе и определение обычной универсальной функции для вызова этого конструктора.