Лучший способ создать экземпляр определенного типа времени выполнения

Какой лучший способ (в .NET 4) создать экземпляр типа, определенного во время выполнения.

У меня есть метод экземпляра, который, хотя действие на объект BaseClass может вызываться экземплярами его производных классов. Мне нужно создать другой экземпляр того же типа, что и this внутри метода. Перегрузка метода для каждого производного класса нецелесообразна, так как она достаточно привлекательна и будет более эффективной для обеспечения единственной реализации.

public class BaseClass
{
     //constructors + properties + methods etc

     public SomeMethod()
     {
          //some code

          DerivedClass d = new DerivedClass(); //ideally determine the DerivedClass type at run-time
     }
}

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

Ответ 1

Вы ищете Activator.CreateInstance (есть и другие перегрузки, такие как этот, который принимает аргументы конструктора). Поэтому вы можете написать

var anotherOneLikeMe = Activator.CreateInstance(this.GetType());

Здесь может возникнуть проблема в том, что anotherOneLikeMe будет набираться как object, поэтому, если вы не намерены передавать его в общий базовый класс (например, BaseClass в вашем примере), вас не так много, что вы может сделать с ним.

Ответ 2

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

static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
 ).Compile();

X x = YCreator();

Статистика (2012):

    Iterations: 5000000
    00:00:00.8481762, Activator.CreateInstance(string, string)
    00:00:00.8416930, Activator.CreateInstance(type)
    00:00:06.6236752, ConstructorInfo.Invoke
    00:00:00.1776255, Compiled expression
    00:00:00.0462197, new

Статистика (2015,.net 4.5, x64):

    Iterations: 5000000
    00:00:00.2659981, Activator.CreateInstance(string, string)
    00:00:00.2603770, Activator.CreateInstance(type)
    00:00:00.7478936, ConstructorInfo.Invoke
    00:00:00.0700757, Compiled expression
    00:00:00.0286710, new

Статистика (2015,.net 4.5, x86):

    Iterations: 5000000
    00:00:00.3541501, Activator.CreateInstance(string, string)
    00:00:00.3686861, Activator.CreateInstance(type)
    00:00:00.9492354, ConstructorInfo.Invoke
    00:00:00.0719072, Compiled expression
    00:00:00.0229387, new

Полный код:

public static X CreateY_New()
{
  return new Y();
}

public static X CreateY_CreateInstance()
{
  return (X)Activator.CreateInstance(typeof(Y));
}

public static X CreateY_CreateInstance_String()
{
  return (X)Activator.CreateInstance("Program", "Y").Unwrap();
}

static readonly System.Reflection.ConstructorInfo YConstructor = 
    typeof(Y).GetConstructor(Type.EmptyTypes);
static readonly object[] Empty = new object[] { };
public static X CreateY_Invoke()
{
  return (X)YConstructor.Invoke(Empty);
}

static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
 ).Compile();
public static X CreateY_CompiledExpression()
{
  return YCreator();
}

static void Main(string[] args)
{
  const int iterations = 5000000;

  Console.WriteLine("Iterations: {0}", iterations);

  foreach (var creatorInfo in new [] 
    { 
      new {Name = "Activator.CreateInstance(string, string)", Creator = (Func<X>)CreateY_CreateInstance},
      new {Name = "Activator.CreateInstance(type)", Creator = (Func<X>)CreateY_CreateInstance},
      new {Name = "ConstructorInfo.Invoke", Creator = (Func<X>)CreateY_Invoke},
      new {Name = "Compiled expression", Creator = (Func<X>)CreateY_CompiledExpression},
      new {Name = "new", Creator = (Func<X>)CreateY_New},
    })
  {
    var creator = creatorInfo.Creator;

    var sum = 0;
    for (var i = 0; i < 1000; i++)
      sum += creator().Z;

    var stopwatch = new Stopwatch();
    stopwatch.Start();
    for (var i = 0; i < iterations; ++i)
    {
      var x = creator();
      sum += x.Z;
    }
    stopwatch.Stop();
    Console.WriteLine("{0}, {1}", stopwatch.Elapsed, creatorInfo.Name);
  }
}

public class X
{
  public X() { }
  public X(int z) { this.Z = z; }
  public int Z;
}
public class Y : X { }

Ответ 3

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

Используйте factory Func:

public void SomeMethod(Func<BaseClass> createDerived)
{
    BaseClass d = createDerived();
}

Создайте свой метод с помощью ограниченного общего типа:

public void SomeMethod<TDerived>() where TDerived : BaseClass, new()
{
    TDerived d = new TDerived();
}

В рамках этой последней альтернативы используется Activator.CreateInstance, как предложили другие. Я предпочитаю последнее отражение, потому что они оба требуют конструктора без параметров, но компилятор принуждает к ограничению, что производный тип должен иметь конструктор без параметров, тогда как подход отражения приводит к исключению среды выполнения.

Ответ 4

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

Однако вы можете делать такие вещи:

BaseClass obj = new DerivedClass();

Это выполняется следующим образом:

BaseClass obj = (BaseClass)Activator.CreateInstance(this.GetType());

Этот вызов завершится с ошибкой, если у DerivedClass нет конструкторов без параметров.

Ответ 5

Это действительно зависит от того, что вы подразумеваете под "runtime" и каковы цели. Например, Джон и Бас оба наткнулись на идею использования Reflection для позднего связывания определенного класса и создания экземпляра в Runtime. Это, безусловно, одна идея, и вы даже можете обнаружить методы на объекте во время выполнения, если это ваша цель.

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

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

Динамическое ключевое слово не то, что вы ищете. Он загружается во время выполнения, но dyanmic больше связан с компилятором, не проверяющим, существуют ли методы, которые вы вызываете. Выполнено неправильно, вы можете получить интересное раздутие, когда вы вызываете метод, который не существует. Основная мотивация, которую я видел для дианамики, - это взаимодействие с динамическим языком, например IronPython.

Ответ 6

public void SomeMethod()
{
    object x =Activator.CreateInstance(this.GetType());
}

Это должно создать новый экземпляр, с другой стороны, я задаюсь вопросом, почему вы пытаетесь это сделать.

Ответ 7

Хотя вам действительно нужно использовать Activator.CreateInstance, вы можете захотеть посмотреть в Activator.CreateInstance(String, String), который можно вызвать, используя имя класса, которое вы, возможно, знаете во время выполнения.

Это будет в основном полезно, если вы создаете производные типы в базовом типе. Если вы собираетесь вызывать SomeMethod из самого производного типа, то предыдущих ответов с использованием this.GetType() должно быть достаточно.