Qual é a melhor maneira de chamar um método genérico quando o parâmetro type não é conhecido no tempo de compilação, mas é obtido dinamicamente no tempo de execução?
Considere o seguinte código de exemplo - dentro do Example()
método, qual é a maneira mais concisa de chamar GenericMethod<T>()
usando o Type
armazenado na myType
variável?
public class Sample
{
public void Example(string typeName)
{
Type myType = FindType(typeName);
// What goes here to call GenericMethod<T>()?
GenericMethod<myType>(); // This doesn't work
// What changes to call StaticMethod<T>()?
Sample.StaticMethod<myType>(); // This also doesn't work
}
public void GenericMethod<T>()
{
// ...
}
public static void StaticMethod<T>()
{
//...
}
}
c#
.net
generics
reflection
Bevan
fonte
fonte
BindingFlags.Instance
, não apenasBindingFlags.NonPublic
, de obter o método privado / interno.Respostas:
Você precisa usar a reflexão para iniciar o método e, em seguida, "construí-lo" fornecendo argumentos de tipo com MakeGenericMethod :
Para um método estático, passe
null
como o primeiro argumento paraInvoke
. Isso não tem nada a ver com métodos genéricos - é apenas uma reflexão normal.Como observado, muito disso é mais simples a partir do C # 4
dynamic
- se é possível usar a inferência de tipo, é claro. Isso não ajuda nos casos em que a inferência de tipo não está disponível, como o exemplo exato da pergunta.fonte
GetMethod()
considera apenas os métodos de instância pública por padrão; portanto, você pode precisarBindingFlags.Static
e / ouBindingFlags.NonPublic
.BindingFlags.NonPublic | BindingFlags.Instance
(e opcionalmenteBindingFlags.Static
).dynamic
não ajuda porque a inferência de tipo não está disponível. (Não há argumentos que o compilador pode usar para determinar o tipo de argumento.)Apenas uma adição à resposta original. Enquanto isso vai funcionar:
Também é um pouco perigoso, pois você perde a verificação em tempo de compilação
GenericMethod
. Se você posteriormente refatorar e renomearGenericMethod
, esse código não notará e falhará no tempo de execução. Além disso, se houver algum pós-processamento do assembly (por exemplo, ofuscar ou remover métodos / classes não utilizados), esse código também poderá ser quebrado.Portanto, se você conhece o método ao qual está vinculando no momento da compilação, e isso não é chamado milhões de vezes, então a sobrecarga não importa, eu alteraria esse código para:
Embora não seja muito bonito, você tem uma referência em tempo de compilação
GenericMethod
aqui e, se você refatorar, excluir ou fazer qualquer coisa comGenericMethod
, esse código continuará funcionando ou, pelo menos, será interrompido no tempo de compilação (se você remover por exemploGenericMethod
).Outra maneira de fazer o mesmo seria criar uma nova classe de wrapper e criá-la
Activator
. Não sei se existe uma maneira melhor.fonte
GenMethod.Method.GetGenericMethodDefinition()
vez dethis.GetType().GetMethod(GenMethod.Method.Name)
. É um pouco mais limpo e provavelmente mais seguro.nameof(GenericMethod)
Chamar um método genérico com um parâmetro de tipo conhecido apenas em tempo de execução pode ser bastante simplificado usando um
dynamic
tipo em vez da API de reflexão.Para usar essa técnica, o tipo deve ser conhecido a partir do objeto real (não apenas uma instância da
Type
classe). Caso contrário, você precisará criar um objeto desse tipo ou usar a solução API de reflexão padrão . Você pode criar um objeto usando o método Activator.CreateInstance .Se você deseja chamar um método genérico, que no uso "normal" teria seu tipo inferido, basta converter o objeto de tipo desconhecido para
dynamic
. Aqui está um exemplo:E aqui está a saída deste programa:
Process
é um método de instância genérico que grava o tipo real do argumento passado (usando oGetType()
método) e o tipo do parâmetro genérico (usando otypeof
operador).Ao converter o argumento do objeto para
dynamic
digitar, adiamos o fornecimento do parâmetro type até o tempo de execução. Quando oProcess
método é chamado com odynamic
argumento, o compilador não se importa com o tipo desse argumento. O compilador gera código que, em tempo de execução, verifica os tipos reais de argumentos passados (usando reflexão) e escolhe o melhor método para chamar. Aqui existe apenas esse método genérico, portanto, ele é chamado com um parâmetro de tipo apropriado.Neste exemplo, a saída é a mesma que se você tivesse escrito:
A versão com um tipo dinâmico é definitivamente mais curta e fácil de escrever. Você também não deve se preocupar com o desempenho de chamar essa função várias vezes. A próxima chamada com argumentos do mesmo tipo deve ser mais rápida, graças ao mecanismo de cache no DLR. Obviamente, você pode escrever um código que armazene em cache os delegados invocados, mas, usando o
dynamic
tipo, você obtém esse comportamento gratuitamente.Se o método genérico que você deseja chamar não tiver um argumento de um tipo parametrizado (portanto, seu parâmetro de tipo não pode ser inferido), você poderá agrupar a invocação do método genérico em um método auxiliar, como no exemplo a seguir:
Maior segurança do tipo
O que é realmente bom em usar o
dynamic
objeto como um substituto para o uso da API de reflexão é que você só perde a verificação do tempo de compilação desse tipo específico que você não conhece até o tempo de execução. Outros argumentos e o nome do método são analisados estaticamente pelo compilador, como de costume. Se você remover ou adicionar mais argumentos, alterar seus tipos ou renomear o nome do método, você receberá um erro em tempo de compilação. Isso não acontecerá se você fornecer o nome do método como uma stringType.GetMethod
e argumentos conforme a matriz de objetosMethodInfo.Invoke
.Abaixo está um exemplo simples que ilustra como alguns erros podem ser detectados em tempo de compilação (código comentado) e outros em tempo de execução. Também mostra como o DLR tenta resolver qual método chamar.
Aqui, novamente, executamos algum método lançando o argumento para o
dynamic
tipo Somente a verificação do tipo do primeiro argumento é adiada para o tempo de execução. Você receberá um erro do compilador se o nome do método que você está chamando não existir ou se outros argumentos forem inválidos (número errado de argumentos ou tipos errados).Quando você passa o
dynamic
argumento para um método, essa chamada é vinculada ultimamente . A resolução de sobrecarga do método ocorre no tempo de execução e tenta escolher a melhor sobrecarga. Portanto, se você chamar oProcessItem
método com um objeto doBarItem
tipo, na verdade, chamará o método não genérico, porque é uma correspondência melhor para esse tipo. No entanto, você receberá um erro de tempo de execução ao passar um argumento doAlpha
tipo porque não há um método que possa manipular esse objeto (um método genérico possui a restriçãowhere T : IItem
e aAlpha
classe não implementa essa interface). Mas esse é o ponto. O compilador não possui informações de que esta chamada é válida. Você, como programador, sabe disso e deve garantir que esse código seja executado sem erros.Gotcha do tipo de retorno
Quando você está chamando um método não nulo com um parâmetro do tipo dinâmico, seu tipo de retorno provavelmente também será
dynamic
. Portanto, se você alterar o exemplo anterior para este código:então o tipo do objeto de resultado seria
dynamic
. Isso ocorre porque o compilador nem sempre sabe qual método será chamado. Se você conhece o tipo de retorno da chamada de função, deve convertê- lo implicitamente no tipo necessário, para que o restante do código seja digitado estaticamente:Você receberá um erro de tempo de execução se o tipo não corresponder.
Na verdade, se você tentar obter o valor do resultado no exemplo anterior, receberá um erro de tempo de execução na segunda iteração do loop. Isso ocorre porque você tentou salvar o valor de retorno de uma função nula.
fonte
ProcessItem
método genérico possui restrição genérica e aceita apenas objetos que implementam aIItem
interface. Quando você ligaráProcessItem(new Aplha(), "test" , 1);
ouProcessItem((object)(new Aplha()), "test" , 1);
obterá um erro do compilador, mas ao transmitir paradynamic
você adiar essa verificação para o tempo de execução.Com o C # 4.0, a reflexão não é necessária, como o DLR pode chamá-lo usando tipos de tempo de execução. Como o uso da biblioteca DLR é meio problemático dinamicamente (em vez do compilador C # que gera código para você), a estrutura de código aberto Dynamitey (.net padrão 1.5) oferece fácil acesso em tempo de execução em cache às mesmas chamadas que o compilador geraria para voce.
fonte
Acrescentando à resposta de Adrian Gallero :
Chamar um método genérico a partir de informações do tipo envolve três etapas.
TLDR: A chamada de um método genérico conhecido com um objeto de tipo pode ser realizada por:
Onde
GenericMethod<object>
é o nome do método a ser chamado e qualquer tipo que atenda às restrições genéricas.(Ação) corresponde à assinatura do método a ser chamado, ou seja, (
Func<string,string,int>
ouAction<bool>
)Etapa 1 é obter o MethodInfo para a definição genérica do método
Método 1: Use GetMethod () ou GetMethods () com tipos apropriados ou sinalizadores de ligação.
Método 2: Crie um delegado, obtenha o objeto MethodInfo e chame GetGenericMethodDefinition
De dentro da classe que contém os métodos:
De fora da classe que contém os métodos:
Em C #, o nome de um método, ou seja, "ToString" ou "GenericMethod", na verdade, refere-se a um grupo de métodos que podem conter um ou mais métodos. Até você fornecer os tipos dos parâmetros do método, não se sabe a qual método você está se referindo.
((Action)GenericMethod<object>)
refere-se ao delegado para um método específico.((Func<string, int>)GenericMethod<object>)
refere-se a uma sobrecarga diferente de GenericMethodMétodo 3: Crie uma expressão lambda contendo uma expressão de chamada de método, obtenha o objeto MethodInfo e, em seguida, GetGenericMethodDefinition
Isso se divide em
Crie uma expressão lambda em que o corpo é uma chamada para o método desejado.
Extraia o corpo e faça a conversão para MethodCallExpression
Obtenha a definição genérica do método no método
A etapa 2 está chamando MakeGenericMethod para criar um método genérico com o (s) tipo (s) apropriado (s).
A etapa 3 é invocar o método com os argumentos apropriados.
fonte
Ninguém forneceu a solução " Reflexão clássica ", então aqui está um exemplo de código completo:
A
DynamicDictionaryFactory
classe acima tem um métodoCreateDynamicGenericInstance(Type keyType, Type valueType)
e cria e retorna uma instância do IDictionary, cujos tipos de chaves e valores são exatamente os especificados na chamada
keyType
evalueType
.Aqui está um exemplo completo de como chamar esse método para instanciar e usar um
Dictionary<String, int>
:Quando o aplicativo de console acima é executado, obtemos o resultado correto e esperado:
fonte
Este é o meu 2 centavos com base na resposta de Grax , mas com dois parâmetros necessários para um método genérico.
Suponha que seu método seja definido da seguinte maneira em uma classe Helpers:
No meu caso, o tipo U é sempre uma coleção observável que armazena objetos do tipo T.
Como tenho meus tipos predefinidos, primeiro crio os objetos "fictícios" que representam a coleção observável (U) e o objeto armazenado nela (T) e que serão usados abaixo para obter seu tipo ao chamar o Make
Em seguida, chame o GetMethod para encontrar sua função genérica:
Até agora, a chamada acima é praticamente idêntica ao que foi explicado acima, mas com uma pequena diferença quando você precisa passar vários parâmetros para ela.
Você precisa passar uma matriz Type [] para a função MakeGenericMethod que contém os tipos de objetos "fictícios" criados acima:
Feito isso, você precisa chamar o método Invoke, conforme mencionado acima.
E você terminou. Funciona um encanto!
ATUALIZAR:
Como o @Bevan destacou, não preciso criar uma matriz ao chamar a função MakeGenericMethod, pois utiliza params e não preciso criar um objeto para obter os tipos, pois posso passar os tipos diretamente para esta função. No meu caso, como tenho os tipos predefinidos em outra classe, simplesmente mudei meu código para:
myClassInfo contém 2 propriedades do tipo
Type
que eu defino em tempo de execução com base em um valor de enum passado para o construtor e fornecerá os tipos relevantes que eu uso no MakeGenericMethod.Mais uma vez obrigado por destacar este @Bevan.
fonte
MakeGenericMethod()
ter a palavra-chave params para que você não precise criar uma matriz; nem você precisa criar instâncias para obter os tipos -methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
seria suficiente.Inspirado pela resposta da Enigmativity - vamos supor que você tenha duas (ou mais) classes, como
e você deseja chamar o método
Foo<T>
comBar
eSquare
, que é declarado comoEntão você pode implementar um método de extensão como:
Com isso, você pode simplesmente invocar
Foo
como:que funciona para todas as classes. Nesse caso, ele produzirá:
fonte