- PVSM.RU - https://www.pvsm.ru -
На первый взгляд, dynamic в C# — просто object с поддержкой машинерии компилятора. Но не совсем.
Ядром времени выполнения является DLR (Dynamic Language Runtime) — подсистема/фреймворк для поддержки динамических языков программирования. Существует реализация под собственно C#, который идет в поставке с .NET и отдельная для Iron-языков.
Когда мы работаем с обобщениями (generics), то CLR имеет свои оптимизации на предмет специализации оных. В тот момент, когда CLR+DLR должны работать с generics вместе, поведение написанного кода может стать непредсказуемым.
Для начала необходимо вспомнить как поддерживаются обобщения CLR'ом.
Каждый generic-тип имеет свою реализацию, т.е. отсутствует type-erasure. Но для ссылочных типов среда использует тип System.__Canon для шаринга кода. Это необходимо не столько из-за очевидности (каждый объект — ссылка размером машинное слово), сколько для разрешения циклической зависимости между типами.
Об этом я уже писал [1]:
Дело в том, что generic-типы могут содержать циклические зависимости от других типов, что чревато бесконечным созданием специализаций для кода. Например:
Generics cyclomatic dependenciesclass GenericClassOne<T> { private T field; } class GenericClassTwo<U> { private GenericClassThree<GenericClassOne<U>> field } class GenericClassThree<S> { private GenericClassTwo<GenericClassOne<S>> field } class Program { static void Main(string[] args) { Console.WriteLine((new GenericClassTwo<object>()).ToString()); Console.Read(); } }
Однако этот код не упадет и выведет GenericClassTwo`1[System.Object].
Type loader (он же загрузчик типов) сканирует каждый generic-тип на наличие циклической зависимости и присваивает очередность (т.н. LoadLevel для класса). Хотя все специализации для ref-types имеют System.__Canon как аргумент типа — это следствие, а не причина.
Фазы загрузки (они же ClassLoadLevel):
enum ClassLoadLevel { CLASS_LOAD_BEGIN, CLASS_LOAD_UNRESTOREDTYPEKEY, CLASS_LOAD_UNRESTORED, CLASS_LOAD_APPROXPARENTS, CLASS_LOAD_EXACTPARENTS, CLASS_DEPENDENCIES_LOADED, CLASS_LOADED, CLASS_LOAD_LEVEL_FINAL = CLASS_LOADED, };
Раз такая особенность существует для обобщений, соответственно и др. подсистемы также должны следовать этому правилу. Но DLR — исключение.
Рассмотрим иерархию классов:
NB: код реальный — из проекта structuremap [2], хоть и претерпевший к этому моменту изменения. Пример использовался во время моего выступления [3] «Эффективное использование DLR».
public class LambdaInstance<T> : LambdaInstance<T, T>
{
}
public class LambdaInstance<T, TPluginType>
: ExpressedInstance<LambdaInstance<T, TPluginType>, T, TPluginType>
{
}
public abstract class ExpressedInstance<T>
{
}
public abstract class ExpressedInstance<T, TReturned, TPluginType> : ExpressedInstance<T>
{
}
И непосредственно код:
class Program
{
static LambdaInstance<object> ShouldThrowException(object argument)
{
throw new NotImplementedException();
}
static void Main(string[] args)
{
// будет ли брошено исключение?
ShouldThrowException((dynamic)new object());
}
}
Вопрос: будет ли брошено исключение?
Ответ: нет. Метод ShouldThrowException никогда не завершится. И stackoverflow (переноса на сайт) не произойдет.
Хм… Так в чем же дело? — спросите Вы.
Все просто — LambdaInstance<object>. Рассмотрим иерархию классов еще раз.
LambdaInstance<T> наследуется от LambdaInstance<T, TPluginType>, который в свою очередь от ExpressedInstance<LambdaInstance<T, TPluginType>, T, TPluginType>.
Вложенное наследование заметили?
Как уже говорилось выше, CLR имеет оптимизацию для циклических зависимостей типов.
Для выражения ShouldThrowException((dynamic)new object());
DLR должен проинспектировать участок кода/сигнатуру метода. В этом процессе встречается LambdaInstance<object> и код превращается в бесконечный цикл.
Почему не крешится? DLR не использует рекурсию. Более того, потребление памяти растет (ибо создаются доп. метаданные), но не сильно.
Может показаться [4], что dynamic как таковой является вещью опасной. В следующий раз мы рассмотрим пример, где его использование — правильно.
Автор: szKarlen
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/117490
Ссылки в тексте:
[1] писал: https://habrahabr.ru/post/253105/
[2] structuremap: http://structuremap.github.io/
[3] выступления: http://msk2014.dotnext.ru/
[4] показаться: https://habrahabr.ru/post/280234/
[5] Источник: https://habrahabr.ru/post/281274/
Нажмите здесь для печати.