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


97

Візьмемо такий приклад як приклад:

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

Відповіді:


142

Я спочатку розмістив цю відповідь тут , але ось передрук, оскільки це не зовсім те саме питання, але має однакову відповідь:

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

Яким би не було значення за замовчуванням для кожного типу, воно буде типовим. Тож об’єкти будуть нульовими, ints 0 тощо. Я думаю, що відбувається будь-яка ініціалізація на рівні класу, але не запускається конструктор.
Джейсон Джексон,

14
@JSBangs, це відмовно, ти наводиш цілком законну відповідь. Ваш коментар та інша відповідь насправді не стосуються поставленого питання. Якщо ви відчуваєте, що маєте кращу відповідь, надайте її. Але відповідь, яку я надав, підкреслює, як використовувати задокументований клас так само, як інші класи серіалізації використовують цей код.
Джейсон Джексон,

21
Служба @JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) не є документальною.
Автодиктакт


72

Використовуйте це перевантаження методу CreateInstance:

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

Створює екземпляр вказаного типу за допомогою конструктора, який найкраще відповідає зазначеним параметрам.

Див .: http://msdn.microsoft.com/en-us/library/wcxyzt4d.aspx


1
Це рішення спрощує проблему. Що робити, якщо я не знаю свого типу, і я кажу "просто створити об’єкт Type у цій змінній Type"?
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

Хороші відповіді, але непридатні для використання в точковій компактній структурі. Ось рішення, яке буде працювати на 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
Це спосіб, який я б назвав конструктором, який не є типовим. Я не впевнений, що хотів би коли-небудь створити об'єкт, не викликаючи взагалі жодного конструктора.
Rory MacLeod

2
Можливо, вам захочеться створити об’єкт без виклику конструкторів, якщо ви пишете власні серіалізатори.
Автодиктакт

1
Так, це саме той сценарій використання, для якого було це питання :)
Айстіна

1
@Aistina Можливо, ви могли б додати цю інформацію до питання? Більшість людей були б проти створення об’єктів, не викликаючи своїх авторів, і витрачали б час, щоб сперечатися з вами з цього приводу, але ваш випадок використання насправді це виправдовує, тому я думаю, що це дуже стосується самого питання.
julealgon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.