Вызов универсального метода с параметром типа, известным только во время выполнения, может быть значительно упрощен при использовании dynamic
типа вместо API отражения.
Чтобы использовать эту технику, тип должен быть известен из фактического объекта (а не только экземпляра Type
класса). В противном случае вам нужно создать объект этого типа или использовать стандартное решение API отражения . Вы можете создать объект, используя метод Activator.CreateInstance .
Если вы хотите вызвать универсальный метод, для которого при «нормальном» использовании был бы сделан вывод о его типе, то это просто сводится к приведению объекта неизвестного типа dynamic
. Вот пример:
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)"
}
}
}
И вот вывод этой программы:
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
является универсальным методом экземпляра, который записывает реальный тип переданного аргумента (с помощью GetType()
метода) и тип универсального параметра (с помощью typeof
оператора).
Приведя аргумент объекта к dynamic
типу, мы отложили предоставление параметра типа до времени выполнения. Когда Process
метод вызывается с dynamic
аргументом, компилятору не важен тип этого аргумента. Компилятор генерирует код, который во время выполнения проверяет реальные типы передаваемых аргументов (используя отражение) и выбирает лучший метод для вызова. Здесь есть только один универсальный метод, поэтому он вызывается с правильным параметром типа.
В этом примере вывод такой же, как если бы вы написали:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
Версия с динамическим типом определенно короче и проще для написания. Вы также не должны беспокоиться о производительности вызова этой функции несколько раз. Следующий вызов с аргументами того же типа должен быть быстрее благодаря механизму кэширования в DLR. Конечно, вы можете написать код, который кеширует вызываемых делегатов, но используя dynamic
тип, вы получаете это поведение бесплатно.
Если универсальный метод, который вы хотите вызвать, не имеет аргумента параметризованного типа (поэтому его параметр типа не может быть выведен), вы можете обернуть вызов универсального метода в вспомогательный метод, как в следующем примере:
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) + ">");
}
}
Повышенная безопасность типов
Что действительно хорошо в использовании dynamic
объекта в качестве замены для использования API отражения, так это то, что вы теряете только проверку времени компиляции этого конкретного типа, которую вы не знаете до времени выполнения. Другие аргументы и имя метода статически анализируются компилятором как обычно. Если вы удалите или добавите больше аргументов, измените их типы или переименуете имя метода, вы получите ошибку во время компиляции. Этого не произойдет, если вы предоставите имя метода в виде строки Type.GetMethod
и аргументы в виде массива объектов MethodInfo.Invoke
.
Ниже приведен простой пример, который иллюстрирует, как некоторые ошибки могут быть обнаружены во время компиляции (закомментированный код), а другие - во время выполнения. Также показано, как DLR пытается определить, какой метод вызывать.
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);
}
}
Здесь мы снова выполняем некоторый метод, приводя аргумент к dynamic
типу. Только проверка типа первого аргумента откладывается до времени выполнения. Вы получите ошибку компилятора, если имя метода, который вы вызываете, не существует или если другие аргументы недопустимы (неправильное количество аргументов или неправильные типы).
Когда вы передаете dynamic
аргумент методу, этот вызов в последнее время становится связанным . Разрешение перегрузки метода происходит во время выполнения и пытается выбрать наилучшую перегрузку. Так что если вы вызываете ProcessItem
метод с объектом BarItem
типа, то вы фактически вызовете неуниверсальный метод, потому что он лучше подходит для этого типа. Однако при передаче аргумента Alpha
типа вы получите ошибку времени выполнения, потому что нет метода, который может обработать этот объект (универсальный метод имеет ограничение, where T : IItem
а Alpha
класс не реализует этот интерфейс). Но в этом все дело. Компилятор не имеет информации, что этот вызов действителен. Вы, как программист, знаете это, и вы должны убедиться, что этот код работает без ошибок.
Возвращаемый тип
Когда вы вызываете не-void метод с параметром динамического типа, его возвращаемый тип, вероятно , dynamic
тоже будет . Так что если вы измените предыдущий пример на этот код:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
тогда тип объекта результата будет dynamic
. Это потому, что компилятор не всегда знает, какой метод будет вызван. Если вы знаете тип возвращаемого значения вызова функции, вам следует неявно преобразовать его в требуемый тип, чтобы остальная часть кода была статически типизирована:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
Вы получите ошибку во время выполнения, если тип не соответствует.
На самом деле, если вы попытаетесь получить значение результата в предыдущем примере, вы получите ошибку времени выполнения во второй итерации цикла. Это потому, что вы пытались сохранить возвращаемое значение функции void.