Создание экземпляра типа без конструктора по умолчанию в 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.