С#: общие типы, у которых есть конструктор?
У меня есть следующий тестовый код С#:
class MyItem
{
MyItem( int a ) {}
}
class MyContainer< T >
where T : MyItem, new()
{
public void CreateItem()
{
T oItem = new T( 10 );
}
}
Visual Studio не может скомпилировать его, ошибка находится в строке, где используется "новое":
'T': cannot provide arguments when creating an instance of a variable type
Возможно ли в С# создать объект родового типа без конструктора без параметров? Это не проблема, чтобы сделать такую вещь в С++-шаблонах, поэтому мне очень любопытно, почему я не могу сделать то же самое в С#. Может быть, какое-то дополнительное "where" требуется или синтаксис отличается?
Ответы
Ответ 1
Это можно сделать с отражением:
public void CreateItem()
{
int constructorparm1 = 10;
T oItem = Activator.CreateInstance(typeof(T), constructorparm1) as T;
}
Но не существует общего ограничения, чтобы гарантировать, что T
реализует желаемый конструктор, поэтому я бы не советовал это делать, если вы не будете осторожны, чтобы объявить этот конструктор в каждом типе, реализующем интерфейс.
Ответ 2
С# и VB.Net в этом отношении не поддерживают понятие ограничения обобщения на наличие конструктора с определенными параметрами. Он поддерживает только ограничение наличия пустого конструктора.
Одна работа должна состоять в том, чтобы вызов вызывал в lambda factory, чтобы создать значение. Например,
public void CreateItem(Func<int,T> del) {
T oItem = del(10);
}
Вызов сайта
CreateItem(x => new SomeClass(x));
Ответ 3
Нет такого общего ограничения, так что это невозможно сразу (это ограничение CLR). Если вы этого хотите, вы должны предоставить класс factory (который имеет конструктор без параметров) и передать его в качестве второго типичного параметра типа.
Ответ 4
IMO, наилучшим подходом здесь является метод инициализации, то есть
interface ISomeInterface {
void Init(int i);
}
class Foo : ISomeInterface {
void ISomeInterface.Init(int i) { /* ... */ }
}
static class Program {
static T Create<T>(int i) where T : class, ISomeInterface, new() {
T t = new T();
t.Init(i);
return t;
}
static void Main() {
Foo foo = Create<Foo>(123);
}
}
Однако вы можете делать то, что хотите, с помощью Expression
(но без поддержки времени компиляции):
using System;
using System.Linq.Expressions;
class Foo {
public Foo(int i) { /* ... */ }
}
static class Program {
static T Create<T>(int i) {
return CtorCache<T>.Create(i);
}
static class CtorCache<T> {
static Func<int, T> ctor;
public static T Create(int i) {
if (ctor == null) ctor = CreateCtor();
return ctor(i);
}
static Func<int, T> CreateCtor() {
var param = Expression.Parameter(typeof(int), "i");
var ci = typeof(T).GetConstructor(new[] {typeof(int)});
if(ci == null) throw new InvalidOperationException("No such ctor");
var body = Expression.New(ci, param);
return Expression.Lambda<Func<int, T>>(body, param).Compile();
}
}
static void Main() {
Foo foo = Create<Foo>(123);
}
}
Обратите внимание, что это кэширует и повторно использует делегат для производительности.
Ответ 5
Один шаблон, который я использую, заключается в том, чтобы связанный класс реализовал интерфейс, который определяет метод Init с соответствующей сигнатурой:
interface IMyItem
{
void Init(int a);
}
class MyItem : IMyItem
{
MyItem() {}
void Init(int a) { }
}
class MyContainer< T >
where T : MyItem, IMyItem, new()
{
public void CreateItem()
{
T oItem = new T();
oItem.Init( 10 );
}
}