Регистрация
15.12.2017
Сообщения
554
Симпатии
483
Баллы
188
#1
Язык C# и среда CLR всегда поддерживали ограниченную форму динамизма в виде вызовов виртуальных методов. Она отличается от динамического связывания C# в том, что для вызовов виртуальных методов компилятор должен фиксировать отдельный виртуальный член на этапе компиляции — основываясь на имени и сигнатуре вызываемого члена. Это означает следующее:

• вызываемые выражения должны быть полностью понимаемы компилятором (например, на этапе компиляции должно приниматься решение о том, чем является целевой член — полем или свойством);

• разрешение перегруженных членов должно полностью осуществляться компилятором на основе типов аргументов во время компиляции.

Следствием последнего утверждения является то, что возможность выполнять вызовы виртуальных методов стала известна как одиночная диспетчеризация. Чтобы понять, почему, взгляните на приведенный ниже вызов метода (Walk — это виртуальный метод):

animal.Walk (owner);

Решение во время выполнения относительно того, какой метод Walk вызывать — класса Dog (собака) или класса Cat (кошка) — зависит только от типа получателя, animal (отсюда и “одиночная”). Если многие перегруженные версии Walk принимают разные виды owner, то перегруженная версия будет выбрана на этапе компиляции безотносительно к тому, каким будет действительный тип объекта owner во время выполнения. Другими словами, только тип времени выполнения получателя может изменить то, какой метод будет вызван.

В противоположность этому, динамический вызов откладывает разрешение перегруженных членов вплоть до времени выполнения:

animal.Walk ((dynamic) owner);

Окончательный выбор того, какой метод Walk будет вызван, теперь зависит и от animal, и от owner — это называется множественной диспетчеризацией, потому что в определении вызываемого метода Walk принимает участие не только тип получателя, но и типы аргументов во время выполнения.

Анонимный вызов членов обобщенного типа

Строгость статической типизации C# — палка о двух концах. С одной стороны, она обеспечивает некоторую степень корректности на этапе компиляции. С другой стороны, иногда она делает некоторые виды кода трудными в реализации или вовсе невозможными, и тогда приходится прибегать к рефлексии. В таких ситуациях динамическое связывание является более чистой и быстрой альтернативой рефлексии.

Примером может служить необходимость работы с объектом типа G, где Т не известен. Это можно проиллюстрировать, определив следующий класс:

Код:
public class Foo { public T Value; }
Предположим, что мы затем записываем метод, как показано ниже:

Код:
static void Write (object obj)

{

if (obj is Foo<>) // Недопустимо

Console.WriteLine ((FooO) obj).Value); // Недопустимо

}

Этот метод не скомпилируется: нельзя вызывать члены несвязанных обобщенных типов. Динамическое связывание предлагает два средства, с помощью которых проблему можно обойти. Первое из них — доступ к члену Value динамическим образом:

Код:
static void Write (dynamic obj)

{

try { Console.WriteLine (obj.Value); }

catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) {...}

}
Это дает (потенциальное) преимущество работы с любым объектом, который определяет поле или свойство Value. Тем не менее, осталась еще пара проблем. Во-первых, перехват исключения подобным образом несколько запутан и неэффективен (к тому же отсутствует возможность заранее спросить у DLR: будет ли эта операция успешной?). Во-вторых, такой подход не будет работать, если Foo является интерфейсом (скажем, IFoo) и справедливо одно из следующих условий:

• Value не реализовано явно;

• тип, который реализует IFoo, не доступен (более подробно об этом речь пойдет ниже).

Лучшее решение заключается в написании перегруженного вспомогательного метода по имени GetFooValue и его вызов с использованием динамического разрешения перегруженных членов.

Код:
static void Write (dynamic obj)

{

object result = GetFooValue (obj);

if (result !=null) Console.WriteLine (result);

}

static T GetFooValue (Foo foo) { return foo.Value; }

static object GetFooValue (object foo) { return null; }

Обратите внимание, что мы перегрузили GetFooValue для приема параметра object, который действует в качестве запасного варианта для любого типа. Во время выполнения динамический связыватель C# выберет лучшую перегруженную версию при вызове GetFooValue с динамическим аргументом. Если рассматриваемый объект не основан на Foo, вместо генерации исключения он будет выбирать перегруженную версию с параметром object.

В качестве альтернативы можно написать только первую перегруженную версию GetFooValue, а затем перехватывать исключение dms RuntimeBinderException. Преимущество такого подхода состоит в том, что он различает случай, когда foo.Value равно null. Недостаток в том, что при этом возникают накладные расходы в плане производительности, связанные с генерацией и перехватом исключения.
 
Похожие темы:
Ответы
1
Просмотры
306
Ответы
1
Просмотры
381
Не актуально Нуждаюсь в программе
Ответы
1
Просмотры
267

Пользователи, которые просматривали тему (Всего: 0)

Тема долгое время не просматривалась.