dynamic
- это просто object
с причудливой шляпой, которая говорит компилятору генерировать проверки типов во время выполнения.Это дает нам одно из фундаментальных правил dynamic
:
Если вы не можете использовать object
в локации, то вы также не можете использовать dynamic
в этой локации.
Вы не можете инициализировать переменную object
с помощью вызова ref something
;Вы должны назначить его переменной ref something
.
Более конкретно: dynamic
предназначен для сценариев, в которых вы взаимодействуете с динамическими объектными моделями, и вам так мало нужна производительность, что вы готовы снова запустить компилятор во время выполнения.«Ref ref» предназначены для строго типобезопасных сценариев, когда вы так заботитесь о производительности, что готовы сделать что-то опасное, например, передать сами переменные в качестве значений.
Это сценарии с противоположными вариантами использования;не пытайтесь использовать их вместе.
В целом: это отличный пример того, насколько сложен современный дизайн языка.Может быть очень, очень трудно заставить работать новую функцию, такую как «возврат возврата», с каждой существующей функцией, добавленной к языку в предыдущем десятилетии .И когда вы добавляете новую функцию, такую как «динамическая», трудно понять, какие проблемы возникнут при добавлении всех функций, которые вы собираетесь добавить в будущем.
Существуют ли другие сценарии, в которых динамические вызовы дают результаты, значительно отличающиеся от их строго типизированных аналогов?
Конечно.Например, поскольку dynamic
- это object
, и поскольку не существует такого понятия, как «тип значения в штучной упаковке», вы можете столкнуться с нечетными ситуациями, когда у вас есть T?
и преобразовать его в dynamic
.Вы не можете тогда позвонить .Value
на него, потому что он больше не T?
.Это либо null
, либо T
.
, но есть еще одна деталь, которая не подходит.Возможно, я что-то упустил.Как это выражение refValXTuple.Item1Ref.Item1
из примера работает просто отлично?Она также ничего не присваивает переменной ref
.
Отличный улов.Позвольте мне объяснить.
Как вы заметили, «ref return» - это новая функция для C # 7, но ref
существует с C # 1.0 тремя способами.Один вы поняли, а два, о котором вы могли не знать.
Вы поняли, что вы, конечно, можете передавать ref
или out
аргументы в ref
или out
формальные параметры;это создает псевдоним для переменной, передаваемой в качестве параметра, поэтому формальный и аргумент ссылаются на одну и ту же переменную.
Первый способ, которым вы, возможно, можете не осознавать, что ref
был в языке, на самом деле является примеромвозврата реф;C # иногда генерирует операции над многомерными массивами, вызывая вспомогательные методы, которые возвращают ссылку в массив.Но в языке нет «видимой для пользователя» поверхности.
Второй способ - this
вызова метода для типа значения: ref
,Вот как вы можете изменить вид получателя вызова в изменяемый тип значения!this
является псевдонимом для переменной, содержащей вызов.
Итак, теперь давайте посмотрим на ваш сайт вызовов.Мы упростим это:
bool result = refValXTuple.Item1Ref.Item1 == "whatever";
Хорошо, что здесь произойдет на уровне IL?На высоком уровне нам нужно:
push the left side of the equality
push "whatever"
call string equality
store the result in the local
Что мы собираемся сделать, чтобы вычислить левую часть равенства?
put refValXTuple on the stack
call the getter of Item1Ref with the receiver that's on the stack
Что такое получатель?Это ссылка. Не ref
. Это ссылка на совершенно обычный объект ссылочного типа.
Что он возвращает?Когда мы закончим, ссылка будет popped , и ref ValXTuple<String>
будет нажата.
ОК, что нам нужно, чтобы настроить вызов на Item1
?Это вызов члена типа значения, поэтому нам понадобится ref ValXTuple<String>
в стеке и ... у нас он есть!Аллилуйя, компилятору здесь не нужно выполнять никакой дополнительной работы, чтобы выполнить свое обязательство поместить ref
в стек перед вызовом.
Так вот почему это работает.В этот момент вам нужно ref
в стеке , и у вас есть один .
Соберите все вместе;предположим, что loc.0 содержит ссылку на наш RefXTuple.IL:
// the evaluation stack is empty
ldloc.0
// a reference to the refxtuple is on the stack
callvirt instance !0& class StackOverflow.RefXTuple`1<valuetype StackOverflow.ValXTuple`1<string>>::get_Item1Ref()
// a ref valxtuple is on the stack
call instance !0 valuetype StackOverflow.ValXTuple`1<string>::get_Item1()
// a string is on the stack
ldstr "whatever"
// two strings are on the stack
call bool [mscorlib]System.String::op_Equality(string, string)
// a bool is on the stack
stloc.1
// the result is stored in the local and the stack is empty.
Теперь сравните это с динамическим регистром.Когда вы говорите
bool result = dynXTuple.Item1Ref.Item1 == "whatever"
Это в основном соответствует моральному эквиваленту:
object d0 = dynXTuple;
object d1 = dynamic_property_get(d0, "Item1Ref");
object d2 = dynamic_property_get(d1, "Item1");
object d3 = "whatever"
object d4 = dynamic_equality_check(d2, d3);
bool result = dynamic_conversion_to_bool(d4);
Как вы можете видеть, это не что иное, как призывы к помощникам и присвоения object
переменных.
Если вы хотите увидеть что-то ужасное, взгляните на сгенерированный real IL для вашего динамического выражения;это намного сложнее, чем я изложил здесь, но морально эквивалентно.
Я просто подумал о другом способе выразить это кратко.Обратите внимание:
refValXTuple.Item1Ref.Item1
refValXTuple.Item1Ref
этого выражения классифицируется как переменная, а не значение , потому что это ref
для переменной;это псевдоним..Item1
требует, чтобы получатель был переменной - потому что Item1
может (причудливо!) Мутировать переменную, и поэтому хорошо, что у нас есть переменная в руке.
Напротив, с
dynXTuple.Item1Ref.Item1
подвыражение dynXTuple.Item1Ref
является значением , и, более того, оно должно быть сохранено в object
, чтобы мы могли динамически вызывать.Item1
на этом объекте.Но во время выполнения он оказывается не объектом, и, более того, это даже не то, что мы можем преобразовать в object
.Тип значения, который вы можете пометить, но ref-to-variable-of-value-type не является коробкой.