C #: универсальные типы, которые имеют конструктор? - PullRequest
19 голосов
/ 30 ноября 2009

У меня есть следующий код теста C #:

  class MyItem
  {
    MyItem( int a ) {}
  }

  class MyContainer< T >
    where T : MyItem, new()
  {
    public void CreateItem()
    {
      T oItem = new T( 10 );
    }
  }

Visual Studio не может скомпилировать его, ошибка находится в строке, где используется 'new':

'T': cannot provide arguments when creating an instance of a variable type

Возможно ли в C # создать объект универсального типа с помощью беспараметрического конструктора? Это не проблема делать в шаблонах C ++, поэтому мне очень любопытно, почему я не могу сделать то же самое в C #. Может быть, требуется дополнительное «где» или синтаксис другой?

Ответы [ 5 ]

25 голосов
/ 30 ноября 2009

C #, и VB.Net в этом отношении, не поддерживают понятие ограничения универсального иметь конструктор с определенными параметрами. Он поддерживает только наличие пустого конструктора.

Одно из возможных решений - передача вызывающей стороны в заводскую лямбду для создания значения. Например

public void CreateItem(Func<int,T> del) {
  T oItem = del(10);
}

Позвонить на сайт

CreateItem(x => new SomeClass(x));
16 голосов
/ 30 ноября 2009

Это можно сделать с помощью отражения:

public void CreateItem()
{
  int constructorparm1 = 10;
  T oItem = Activator.CreateInstance(typeof(T), constructorparm1) as T;
}

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

9 голосов
/ 30 ноября 2009

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

4 голосов
/ 01 декабря 2009

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);
    }
}

Обратите внимание, что это кэширует и повторно использует делегата для производительности.

2 голосов
/ 30 ноября 2009

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