#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();
}
Если в вашем классе есть какие-либо поля универсального типа, у вас нет выбора, кроме как поместить все эти типы в класс. Однако, если вы просто хотели скрыть какой-то тип от конструктора, вам нужно будет использовать вышеупомянутый трюк — наличие скрытого конструктора для объединения всего вместе и определение обычной универсальной функции для вызова этого конструктора.