La chiamata di un metodo generico con un parametro di tipo noto solo in fase di esecuzione può essere notevolmente semplificata utilizzando a dynamic
tipo anziché l'API di reflection.
Per utilizzare questa tecnica è necessario conoscere il tipo dall'oggetto reale (non solo un'istanza della Type
classe). Altrimenti, è necessario creare un oggetto di quel tipo o utilizzare la soluzione API di riflessione standard . È possibile creare un oggetto utilizzando Activator.CreateInstance metodo .
Se si desidera chiamare un metodo generico, che nell'uso "normale" si sarebbe dedotto il suo tipo, allora si tratta semplicemente di lanciare l'oggetto di tipo sconosciuto dynamic
. Ecco un esempio:
class Alpha { }
class Beta { }
class Service
{
public void Process<T>(T item)
{
Console.WriteLine("item.GetType(): " + item.GetType()
+ "\ttypeof(T): " + typeof(T));
}
}
class Program
{
static void Main(string[] args)
{
var a = new Alpha();
var b = new Beta();
var service = new Service();
service.Process(a); // Same as "service.Process<Alpha>(a)"
service.Process(b); // Same as "service.Process<Beta>(b)"
var objects = new object[] { a, b };
foreach (var o in objects)
{
service.Process(o); // Same as "service.Process<object>(o)"
}
foreach (var o in objects)
{
dynamic dynObj = o;
service.Process(dynObj); // Or write "service.Process((dynamic)o)"
}
}
}
Ed ecco l'output di questo programma:
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
item.GetType(): Alpha typeof(T): System.Object
item.GetType(): Beta typeof(T): System.Object
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
Process
è un metodo di istanza generico che scrive il tipo reale dell'argomento passato (usando il GetType()
metodo) e il tipo del parametro generico (usando l' typeof
operatore).
Trasmettendo l'argomento object da dynamic
digitare abbiamo rinviato fornendo il parametro type fino al runtime. Quando il Process
metodo viene chiamato con ildynamic
argomento, al compilatore non interessa il tipo di argomento. Il compilatore genera codice che in fase di runtime verifica i tipi reali di argomenti passati (usando la riflessione) e sceglie il metodo migliore da chiamare. Qui esiste solo questo metodo generico, quindi viene invocato con un parametro di tipo appropriato.
In questo esempio, l'output è lo stesso di se si scrivesse:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
La versione con un tipo dinamico è decisamente più breve e più facile da scrivere. Inoltre, non dovresti preoccuparti delle prestazioni di chiamata a questa funzione più volte. La chiamata successiva con argomenti dello stesso tipo dovrebbe essere più veloce grazie al meccanismo di memorizzazione nella cache in DLR. Ovviamente, puoi scrivere codice che memorizza nella cache i delegati invocati, ma utilizzando il dynamic
tipo ottieni questo comportamento gratuitamente.
Se il metodo generico che vuoi chiamare non ha un argomento di tipo parametrizzato (quindi il suo parametro di tipo non può essere dedotto), puoi avvolgere l'invocazione del metodo generico in un metodo di supporto come nell'esempio seguente:
class Program
{
static void Main(string[] args)
{
object obj = new Alpha();
Helper((dynamic)obj);
}
public static void Helper<T>(T obj)
{
GenericMethod<T>();
}
public static void GenericMethod<T>()
{
Console.WriteLine("GenericMethod<" + typeof(T) + ">");
}
}
Maggiore sicurezza del tipo
La cosa fantastica dell'utilizzo dynamic
dell'oggetto come sostituto dell'utilizzo dell'API reflection è che si perde solo il controllo della compilazione di questo particolare tipo che non si conosce fino al runtime. Altri argomenti e il nome del metodo vengono analizzati staticamente dal compilatore come al solito. Se rimuovi o aggiungi altri argomenti, ne cambi i tipi o rinomini il nome del metodo, otterrai un errore di compilazione. Ciò non accadrà se si fornisce il nome del metodo come stringa Type.GetMethod
e argomenti come l'array di oggetti MethodInfo.Invoke
.
Di seguito è riportato un semplice esempio che illustra come è possibile rilevare alcuni errori in fase di compilazione (codice commentato) e altri in fase di esecuzione. Mostra anche come il DLR tenta di risolvere quale metodo chiamare.
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }
class Program
{
static void Main(string[] args)
{
var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
for (int i = 0; i < objects.Length; i++)
{
ProcessItem((dynamic)objects[i], "test" + i, i);
//ProcesItm((dynamic)objects[i], "test" + i, i);
//compiler error: The name 'ProcesItm' does not
//exist in the current context
//ProcessItem((dynamic)objects[i], "test" + i);
//error: No overload for method 'ProcessItem' takes 2 arguments
}
}
static string ProcessItem<T>(T item, string text, int number)
where T : IItem
{
Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
typeof(T), text, number);
return "OK";
}
static void ProcessItem(BarItem item, string text, int number)
{
Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
}
}
Qui eseguiamo nuovamente un metodo lanciando l'argomento sul dynamic
tipo. Solo la verifica del tipo del primo argomento è posticipata al runtime. Riceverai un errore del compilatore se il nome del metodo che stai chiamando non esiste o se altri argomenti non sono validi (numero errato di argomenti o tipi errati).
Quando si passa l' dynamic
argomento a un metodo, questa chiamata viene recentemente associata . La risoluzione del sovraccarico del metodo si verifica in fase di esecuzione e cerca di scegliere il sovraccarico migliore. Quindi, se invochi il ProcessItem
metodo con un oggetto di BarItem
tipo, in realtà chiamerai il metodo non generico, perché è una corrispondenza migliore per questo tipo. Tuttavia, otterrai un errore di runtime quando passi un argomento del Alpha
tipo perché non esiste un metodo in grado di gestire questo oggetto (un metodo generico ha il vincolo where T : IItem
e la Alpha
classe non implementa questa interfaccia). Ma questo è il punto. Il compilatore non ha informazioni sulla validità di questa chiamata. Come programmatore lo sai e dovresti assicurarti che questo codice venga eseguito senza errori.
Tipo di ritorno gotcha
Quando chiami un metodo non vuoto con un parametro di tipo dinamico, probabilmente lo sarà dynamic
anche il suo tipo restituito . Quindi, se cambiassi l'esempio precedente in questo codice:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
quindi il tipo dell'oggetto risultato sarebbe dynamic
. Questo perché il compilatore non sempre sa quale metodo verrà chiamato. Se si conosce il tipo restituito della chiamata di funzione, è necessario convertirlo in modo implicito nel tipo richiesto in modo che il resto del codice venga digitato staticamente:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
Riceverai un errore di runtime se il tipo non corrisponde.
In realtà, se si tenta di ottenere il valore del risultato nell'esempio precedente, si otterrà un errore di runtime nella seconda iterazione del ciclo. Questo perché si è tentato di salvare il valore restituito di una funzione void.