Создание экземпляра типа без конструктора по умолчанию в C # с использованием отражения

98

В качестве примера возьмем следующий класс:

class Sometype
{
    int someValue;

    public Sometype(int someValue)
    {
        this.someValue = someValue;
    }
}

Затем я хочу создать экземпляр этого типа, используя отражение:

Type t = typeof(Sometype);
object o = Activator.CreateInstance(t);

Обычно это сработает, однако, поскольку SomeTypeне определен конструктор без параметров, вызов вызывает Activator.CreateInstanceисключение типа MissingMethodExceptionс сообщением « Для этого объекта не определен конструктор без параметров ». Есть ли альтернативный способ по-прежнему создать экземпляр этого типа? Было бы отстойно добавлять конструкторы без параметров ко всем моим классам.

Аистина
источник
2
FormatterServices.GetUninitializedObjectне позволять создавать неинициализированную строку. Вы можете получить исключение: System.ArgumentException: Uninitialized Strings cannot be created.помните об этом.
Bartosz Pierzchlewicz
Спасибо за внимание, но я уже обрабатываю строки и базовые типы отдельно.
Aistina

Ответы:

143

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

FormatterServices.GetUninitializedObject()создаст экземпляр без вызова конструктора. Я нашел этот класс, используя Reflector и покопавшись в некоторых основных классах сериализации .Net.

Я протестировал его, используя приведенный ниже пример кода, и похоже, что он отлично работает:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.Serialization;

namespace NoConstructorThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass)); //does not call ctor
            myClass.One = 1;
            Console.WriteLine(myClass.One); //write "1"
            Console.ReadKey();
        }
    }

    public class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("MyClass ctor called.");
        }

        public int One
        {
            get;
            set;
        }
    }
}
Джейсон Джексон
источник
Отлично, похоже, это именно то, что мне нужно. Я предполагаю, что неинициализированный означает, что вся его память будет обнулена? (Подобно тому, как создаются экземпляры структур)
Aistina
Значение по умолчанию для каждого типа будет значением по умолчанию. Таким образом, объекты будут иметь значение null, int 0 и т. Д. Я думаю, что любая инициализация уровня класса происходит, но конструктор не запускается.
Джейсон Джексон,
14
@JSBangs, отстой, вы даете вполне законный ответ. Ваш комментарий и другой ответ на самом деле не затрагивают заданный вопрос. Если вы чувствуете, что у вас есть лучший ответ, дайте его. Но в ответе, который я дал, подчеркивается, как использовать документированный класс так же, как другие классы сериализации используют этот код.
Джейсон Джексон
21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) не недокументирован.
Autodidact
72

Используйте эту перегрузку метода CreateInstance:

public static Object CreateInstance(
    Type type,
    params Object[] args
)

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

См .: http://msdn.microsoft.com/en-us/library/wcxyzt4d.aspx

Ник
источник
1
Это решение упрощает проблему. Что делать, если я не знаю свой тип и говорю «просто создайте объект типа в этой переменной типа»?
kamii
23

Когда я тестировал производительность, (T)FormatterServices.GetUninitializedObject(typeof(T))он был медленнее. В то же время скомпилированные выражения дадут вам значительное увеличение скорости, хотя они работают только для типов с конструктором по умолчанию. Я использовал гибридный подход:

public static class New<T>
{
    public static readonly Func<T> Instance = Creator();

    static Func<T> Creator()
    {
        Type t = typeof(T);
        if (t == typeof(string))
            return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile();

        if (t.HasDefaultConstructor())
            return Expression.Lambda<Func<T>>(Expression.New(t)).Compile();

        return () => (T)FormatterServices.GetUninitializedObject(t);
    }
}

public static bool HasDefaultConstructor(this Type t)
{
    return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}

Это означает, что выражение create эффективно кэшируется и влечет за собой штраф только при первой загрузке типа. Будет также эффективно обрабатывать типы значений.

Назови это:

MyType me = New<MyType>.Instance();

Обратите внимание, что (T)FormatterServices.GetUninitializedObject(t)для строки не удастся. Следовательно, для возврата пустой строки применяется специальная обработка строки.

науфал
источник
1
Странно, как взгляд на одну строчку чьего-то кода может спасти день. Спасибо, сэр! Причины производительности привели меня к вашему посту, и фокус готов :) Классы FormatterServices и Activator неэффективны по сравнению с скомпилированными выражениями, как жаль, что активаторы повсюду.
jmodrak
@nawfal Что касается вашей специальной обработки строки, я знаю, что она не удалась бы для строки без этой специальной обработки, но я просто хочу знать: будет ли она работать для всех других типов?
Sнаđошƒаӽ
@ Sнаđошƒаӽ к сожалению нет. Данный пример является базовым, и .NET имеет много различных типов типов. Например, подумайте, если вы передадите тип делегата, как вы предоставите ему экземпляр? Или иначе, если конструктор выдает, что вы можете с этим поделать? Много разных способов справиться с этим. С тех пор, как я ответил на это, обновился, чтобы обрабатывать гораздо больше сценариев в моей библиотеке. Он пока нигде не опубликован.
nawfal
4

Хорошие ответы, но непригодные для использования на компактной платформе dot net. Вот решение, которое будет работать на CF.Net ...

class Test
{
    int _myInt;

    public Test(int myInt)
    {
        _myInt = myInt;
    }

    public override string ToString()
    {
        return "My int = " + _myInt.ToString();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var ctor = typeof(Test).GetConstructor(new Type[] { typeof(int) });
        var obj = ctor.Invoke(new object[] { 10 });
        Console.WriteLine(obj);
    }
}
Самоучка
источник
1
Так я бы назвал конструктор не по умолчанию. Я не уверен, что когда-нибудь захочу создать объект, вообще не вызывая конструктор.
Рори МакЛауд
2
Вы можете создать объект без вызова конструкторов, если пишете собственные сериализаторы.
Autodidact
1
Ага, это точный сценарий использования, для которого был задан вопрос :)
Аистина
1
@Aistina Возможно, вы могли бы добавить эту информацию к вопросу? Большинство людей были бы против создания объектов без обращения к своим ctors и потратили бы время, чтобы спорить с вами по этому поводу, но ваш вариант использования действительно оправдывает это, поэтому я думаю, что это очень актуально для самого вопроса.
julealgon