Criação de instância do tipo sem construtor padrão em C # usando reflexão

97

Tome a seguinte aula como exemplo:

class Sometype
{
    int someValue;

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

Em seguida, quero criar uma instância desse tipo usando reflexão:

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

Normalmente, isso funcionará, no entanto, como SomeTypenão definiu um construtor sem parâmetros, a chamada para Activator.CreateInstancelançará uma exceção do tipo MissingMethodExceptioncom a mensagem " Nenhum construtor sem parâmetros definido para este objeto. " Existe uma maneira alternativa de ainda criar uma instância desse tipo? Seria meio chato adicionar construtores sem parâmetros a todas as minhas classes.

Aistina
fonte
2
FormatterServices.GetUninitializedObjectnão permite a criação de strings não inicializadas. Você pode obter uma exceção: System.ArgumentException: Uninitialized Strings cannot be created.tenha isso em mente.
Bartosz Pierzchlewicz de
Obrigado pelo aviso, mas já estou lidando com strings e tipos básicos separadamente.
Aistina

Respostas:

142

Originalmente, postei esta resposta aqui , mas aqui está uma reimpressão, pois esta não é exatamente a mesma pergunta, mas tem a mesma resposta:

FormatterServices.GetUninitializedObject()criará uma instância sem chamar um construtor. Eu encontrei essa classe usando o Reflector e vasculhando algumas das principais classes de serialização .Net.

Eu testei usando o código de amostra abaixo e parece que funciona muito bem:

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;
        }
    }
}
Jason Jackson
fonte
Incrível, parece que é exatamente o que eu preciso. Presumo que não inicializado significa que toda a sua memória será definida como zeros. (Semelhante à forma como as estruturas são instanciadas)
Aistina
Qualquer que seja o valor padrão para cada tipo, será o padrão. Portanto, os objetos serão nulos, ints 0, etc. Eu realmente acho que qualquer inicialização em nível de classe ocorre, mas nenhum construtor é executado.
Jason Jackson
14
@JSBangs, Que pena que você está dando uma resposta perfeitamente legítima. Seu comentário e a outra resposta não respondem realmente à pergunta feita. Se você acha que tem uma resposta melhor, forneça uma. Mas a resposta que forneci destaca como usar uma classe documentada da mesma forma que outras classes de serialização usam esse código.
Jason Jackson
21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) não está documentado.
Autodidata
72

Use esta sobrecarga do método CreateInstance:

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

Cria uma instância do tipo especificado usando o construtor que melhor corresponde aos parâmetros especificados.

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

usuario
fonte
1
Essa solução simplifica demais o problema. E se eu não souber meu tipo e estiver dizendo "basta criar um objeto do tipo nesta variável de tipo"?
kamii
23

Quando fiz o benchmarking, o desempenho (T)FormatterServices.GetUninitializedObject(typeof(T))foi mais lento. Ao mesmo tempo, as expressões compiladas proporcionariam grandes melhorias de velocidade, embora funcionem apenas para tipos com construtor padrão. Adotei uma abordagem híbrida:

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

Isso significa que a expressão de criação é efetivamente armazenada em cache e incorre em penalidades apenas na primeira vez que o tipo é carregado. Também lidará com tipos de valor de maneira eficiente.

Chame-o:

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

Observe que (T)FormatterServices.GetUninitializedObject(t)falhará para string. Conseqüentemente, o tratamento especial para string está disponível para retornar uma string vazia.

Nawfal
fonte
1
É estranho como olhar para uma linha do código de alguém pode salvar um dia. Obrigado, senhor! Motivos de desempenho me levaram ao seu post e o truque está feito :) As classes FormatterServices e Activator estão com baixo desempenho em comparação com expressões compiladas, que pena encontrar Ativadores em todo lugar.
jmodrak
@nawfal Quanto ao seu tratamento especial para cordas, eu sei que ele não funcionaria para cordas sem esse tratamento especial, mas só quero saber: vai funcionar para todos os outros tipos?
Sнаđошƒаӽ
@ Sнаđошƒаӽ infelizmente não. O exemplo fornecido é barebones e .NET tem muitos tipos diferentes de tipos. Por exemplo, considere, se você passar um tipo de delegado, como você lhe dará uma instância? Ou então, se o construtor lançar, o que você pode fazer a respeito? Muitas maneiras diferentes de lidar com isso. Eu tinha respondido a esta atualização para lidar com muito mais cenários em minha biblioteca. Não foi publicado em lugar nenhum por enquanto.
nawfal
4

Boas respostas, mas inutilizáveis ​​na estrutura compacta dot net. Aqui está uma solução que funcionará na 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);
    }
}
Autodidata
fonte
1
Essa é a maneira que eu chamaria de construtor não padrão. Não tenho certeza se gostaria de criar um objeto sem chamar nenhum construtor.
Rory MacLeod
2
Você pode desejar criar um objeto sem chamar construtores se estiver escrevendo serializadores personalizados.
Autodidata de
1
Sim, esse é o cenário de caso de uso exato para o qual esta pergunta foi feita :)
Aistina
1
@Aistina Talvez você pudesse adicionar essa informação à pergunta? A maioria das pessoas seria contra a criação de objetos sem chamar seus ctors e perderia tempo discutindo com você sobre isso, mas seu caso de uso na verdade justifica isso, então acho que é muito relevante para a própria questão.
julealgon