В C # 4.0 появился новый тип под названием «динамический». Все это звучит хорошо, но зачем программисту это использовать?
Есть ли ситуация, когда это может спасти день?
В C # 4.0 появился новый тип под названием «динамический». Все это звучит хорошо, но зачем программисту это использовать?
Есть ли ситуация, когда это может спасти день?
Ответы:
Ключевое слово dynamic является новым для C # 4.0 и используется, чтобы сообщить компилятору, что тип переменной может измениться или что она неизвестна до времени выполнения. Думайте об этом как о возможности взаимодействовать с Объектом без необходимости его наложения.
dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!
Обратите внимание, что нам не нужно ни приводить, ни объявлять cust как тип Customer. Поскольку мы объявили его динамическим, среда выполнения вступает во владение, а затем ищет и задает нам свойство FirstName. Теперь, конечно, когда вы используете динамическую переменную, вы отказываетесь от проверки типа компилятора. Это означает, что вызов cust.MissingMethod () будет скомпилирован и не завершится с ошибкой до времени выполнения. Результатом этой операции является исключение RuntimeBinderException, поскольку MissingMethod не определен в классе Customer.
В приведенном выше примере показано, как работает динамический при вызове методов и свойств. Еще одна мощная (и потенциально опасная) функция - возможность многократного использования переменных для разных типов данных. Я уверен, что программисты на Python, Ruby и Perl могут придумать миллион способов воспользоваться этим, но я использовал C # так долго, что мне это кажется «неправильным».
dynamic foo = 123;
foo = "bar";
ОК, так что вы, скорее всего, не будете часто писать код, подобный приведенному выше. Однако могут быть случаи, когда повторное использование переменных может пригодиться или очистить грязный фрагмент устаревшего кода. Один простой случай, с которым я часто сталкиваюсь, это постоянное приведение между десятичным и двойным.
decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");
Вторая строка не компилируется, потому что 2.5 имеет тип double, а строка 3 не компилируется, потому что Math.Sqrt ожидает double. Очевидно, что все, что вам нужно сделать, это привести и / или изменить тип переменной, но могут быть ситуации, когда имеет смысл использовать динамический.
dynamic foo = GetDecimalValue(); // still returns a decimal
foo = foo / 2.5; // The runtime takes care of this for us
foo = Math.Sqrt(foo); // Again, the DLR works its magic
string bar = foo.ToString("c");
Подробнее читайте: http://www.codeproject.com/KB/cs/CSharp4Features.aspx
dynamic
in c # для решения проблем, которые могут быть решены (возможно, даже лучше) стандартными функциями c # и статической типизацией, или, самое большее, с помощью inference типа ( var
). dynamic
следует использовать только когда речь идет о проблемах взаимодействия с DLR. Если вы пишете код на статическом типизированном языке, таком как c # is, то делайте это и не эмулируйте динамический язык. Это просто безобразно.
dynamic
переменные в своем коде там, где они вам не нужны (как в вашем примере с квадратным корнем), вы прекращаете чистую проверку ошибок времени компиляции; вместо этого вы теперь получаете возможные ошибки во время выполнения.
dynamic
Ключевое слово добавлено, вместе со многими другими новыми функциями C # 4.0, чтобы сделать его проще говорить с кодом , который живет в или приходит от других сред исполнения, который имеет различные интерфейсы.
Возьми пример.
Если у вас есть COM-объект, такой как Word.Application
объект, и вы хотите открыть документ, метод для этого содержит не менее 15 параметров, большинство из которых являются необязательными.
Чтобы вызвать этот метод, вам нужно что-то вроде этого (я упрощаю, это не настоящий код):
object missing = System.Reflection.Missing.Value;
object fileName = "C:\\test.docx";
object readOnly = true;
wordApplication.Documents.Open(ref fileName, ref missing, ref readOnly,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing);
Обратите внимание на все эти аргументы? Вы должны передать их, так как C # до версии 4.0 не имел понятия необязательных аргументов. В C # 4.0 с COM API стало проще работать, введя:
ref
необязательным для COM APIНовый синтаксис для вышеуказанного вызова будет:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
Видите, насколько легче это выглядит, насколько более читабельным это становится?
Давайте разберем это на части:
named argument, can skip the rest
|
v
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
^ ^
| |
notice no ref keyword, can pass
actual parameter values instead
Волшебство в том, что компилятор C # теперь будет вводить необходимый код и работать с новыми классами во время выполнения, чтобы сделать почти то же самое, что вы делали раньше, но синтаксис был скрыт от вас, теперь вы можете сосредоточиться на что , и не так много о том, как . Андерс Хейлсберг любит говорить, что вы должны вызывать различные «заклинания», что является своего рода каламбуром в магии всего этого, когда вы обычно должны махать рукой (руками) и произносить магические слова в правильном порядке. чтобы получить заклинание определенного типа. Старый API-способ общения с COM-объектами заключался в том, что вам нужно было прыгать через множество циклов, чтобы уговорить компилятор скомпилировать код для вас.
В C # до версии 4.0 дела обстоят хуже, даже если вы попытаетесь поговорить с COM-объектом, для которого у вас нет интерфейса или класса, все, что у вас есть, - это IDispatch
ссылка.
Если вы не знаете, что это такое, IDispatch
это в основном отражение для объектов COM. С помощью IDispatch
интерфейса вы можете задать объекту «что такое номер идентификатора для метода, известного как Save», и создать массивы определенного типа, содержащие значения аргументов, и, наконец, вызвать Invoke
метод IDispatch
интерфейса для вызова метода, передав все информация, которую вам удалось собрать вместе.
Приведенный выше метод Save может выглядеть следующим образом (это определенно неправильный код):
string[] methodNames = new[] { "Open" };
Guid IID = ...
int methodId = wordApplication.GetIDsOfNames(IID, methodNames, methodNames.Length, lcid, dispid);
SafeArray args = new SafeArray(new[] { fileName, missing, missing, .... });
wordApplication.Invoke(methodId, ... args, ...);
Все это только для открытия документа.
VB имел необязательные аргументы и поддержку большей части этого из коробки давно, поэтому этот код C #:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
в основном просто C # догоняет VB с точки зрения выразительности, но делает это правильно, делая его расширяемым, а не только для COM. Конечно, это также доступно для VB.NET или любого другого языка, построенного поверх среды выполнения .NET.
Вы можете найти больше информации об IDispatch
интерфейсе в Википедии: IDispatch если вы хотите узнать больше об этом. Это действительно ужасные вещи.
Однако, что если вы хотите поговорить с объектом Python? Для этого есть другой API, чем тот, который используется для COM-объектов, и, поскольку объекты Python также являются динамическими по своей природе, вам нужно прибегнуть к магии отражения, чтобы найти правильные методы для вызова, их параметры и т. Д., Но не .NET отражение, что-то написанное для Python, в значительной степени похожее на приведенный выше код IDispatch, просто совсем другое.
А для Руби? Другой API все еще.
JavaScript? То же самое, другой API для этого тоже.
Ключевое слово dynamic состоит из двух вещей:
dynamic
dynamic
требуется для ключевого слова, и сопоставляют вызовы с правильным способом выполнения действий. API даже задокументирован, поэтому, если у вас есть объекты, полученные из незавершенной среды выполнения, вы можете добавить их.Однако dynamic
ключевое слово не предназначено для замены какого-либо существующего кода .NET. Конечно, вы можете сделать это, но он не был добавлен по этой причине, и авторы языка программирования C # с Андерсом Хейлсбергом впереди, были непреклонны в том, что они по-прежнему рассматривают C # как строго типизированный язык, и не будут жертвовать этот принцип.
Это означает, что хотя вы можете написать код так:
dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;
и чтобы он был скомпилирован, он не был задуман как своего рода система типа «давай выясним, что ты имел в виду во время выполнения».
Целью было облегчить общение с другими типами предметов.
В Интернете много материала о ключевых словах, сторонниках, противниках, обсуждениях, недовольстве, похвале и т. Д.
Я предлагаю вам начать со следующих ссылок, а затем Google для получения дополнительной информации:
dynamic
было добавлено, чтобы поддержать другие экосистемы в том, как может быть выполнен вызов метода, подобного отражению, а также обеспечить своего рода подход черного ящика к структурам данных с документированным способом достижения этого.
Я удивлен, что никто не упомянул многократную рассылку . Обычный способ обойти это через шаблон посетителя, и это не всегда возможно, так что вы в конечном итоге с накоплениемis
чеки .
Итак, вот реальный пример применения моего собственного приложения. Вместо того, чтобы делать:
public static MapDtoBase CreateDto(ChartItem item)
{
if (item is ElevationPoint) return CreateDtoImpl((ElevationPoint)item);
if (item is MapPoint) return CreateDtoImpl((MapPoint)item);
if (item is MapPolyline) return CreateDtoImpl((MapPolyline)item);
//other subtypes follow
throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
Вы делаете:
public static MapDtoBase CreateDto(ChartItem item)
{
return CreateDtoImpl(item as dynamic);
}
private static MapDtoBase CreateDtoImpl(ChartItem item)
{
throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
private static MapDtoBase CreateDtoImpl(MapPoint item)
{
return new MapPointDto(item);
}
private static MapDtoBase CreateDtoImpl(ElevationPoint item)
{
return new ElevationDto(item);
}
Обратите внимание, что в первом случае ElevationPoint
это подкласс класса, MapPoint
и если он не размещен до MapPoint
этого, он никогда не будет достигнут. В случае с динамическим это не так, так как будет вызван ближайший метод сопоставления.
Как можно догадаться из кода, эта функция пригодилась, когда я выполнял перевод объектов ChartItem в их сериализуемые версии. Я не хотел загрязнять свой код посетителями и не хотел также загрязнять свои ChartItem
объекты бесполезными специфическими атрибутами сериализации.
is
сложены один поверх другого.
magic
; нет такой вещи как магия.
Для статических типизированных языков (CLR) легче взаимодействовать с динамическими (python, ruby ...), работающими на DLR (динамическая среда исполнения языка), см. MSDN :
Например, вы можете использовать следующий код для увеличения счетчика в XML в C #.
Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);
Используя DLR, вы можете использовать следующий код вместо той же операции.
scriptobj.Count += 1;
MSDN перечисляет эти преимущества:
- Упрощает перенос динамических языков на .NET Framework
- Включает динамические функции в статически типизированных языках
- Обеспечивает будущие преимущества DLR и .NET Framework
- Включает общий доступ к библиотекам и объектам
- Обеспечивает быструю динамическую отправку и вызов
Смотрите MSDN для более подробной информации.
Пример использования:
Вы потребляете много классов, у которых есть общее свойство CreationDate:
public class Contact
{
// some properties
public DateTime CreationDate { get; set; }
}
public class Company
{
// some properties
public DateTime CreationDate { get; set; }
}
public class Opportunity
{
// some properties
public DateTime CreationDate { get; set; }
}
Если вы напишите метод commun, который извлекает значение свойства 'CreationDate', вам придется использовать отражение:
static DateTime RetrieveValueOfCreationDate(Object item)
{
return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
}
С «динамической» концепцией ваш код выглядит намного элегантнее:
static DateTime RetrieveValueOfCreationDate(dynamic item)
{
return item.CreationDate;
}
Он будет в основном использоваться жертвами RAD и Python для разрушения качества кода, IntelliSense и обнаружения ошибок времени компиляции.
Он оценивает во время выполнения, так что вы можете переключать тип, как вы можете в JavaScript, на что угодно. Это законно
dynamic i = 12;
i = "text";
И поэтому вы можете изменить тип, как вам нужно. Используйте это как последнее средство; это полезно, но я слышал, что многое происходит за кулисами с точки зрения генерируемого ИЛ, и это может стоить производительности.
Лучший вариант использования переменных типа «динамический» для меня был, когда недавно я писал слой доступа к данным в ADO.NET ( с использованием SQLDataReader ), а код вызывал уже написанные унаследованные хранимые процедуры. Существуют сотни таких устаревших хранимых процедур, содержащих большую часть бизнес-логики. Мой уровень доступа к данным должен был возвращать какие-то структурированные данные на уровень бизнес-логики, основанный на C #, для выполнения некоторых манипуляций ( хотя их почти нет ). Каждая хранимая процедура возвращает различный набор данных ( столбцы таблицы ). Поэтому вместо того, чтобы создавать десятки классов или структур для хранения возвращаемых данных и передачи их в BLL, я написал приведенный ниже код, который выглядит довольно элегантно и аккуратно.
public static dynamic GetSomeData(ParameterDTO dto)
{
dynamic result = null;
string SPName = "a_legacy_stored_procedure";
using (SqlConnection connection = new SqlConnection(DataConnection.ConnectionString))
{
SqlCommand command = new SqlCommand(SPName, connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("@empid", dto.EmpID));
command.Parameters.Add(new SqlParameter("@deptid", dto.DeptID));
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
dynamic row = new ExpandoObject();
row.EmpName = reader["EmpFullName"].ToString();
row.DeptName = reader["DeptName"].ToString();
row.AnotherColumn = reader["AnotherColumn"].ToString();
result = row;
}
}
}
return result;
}
dynamic np = Py.Import("numpy")
dynamic
при применении к ним числовых операторов. Это обеспечивает безопасность типов и позволяет избежать ограничений генериков. Это по сути * утка набирает:T y = x * (dynamic)x
, где typeof(x) is T
Другой вариант использования для dynamic
набора текста - для виртуальных методов, которые сталкиваются с проблемой ковариации или контравариантности. Одним из таких примеров является печально известный Clone
метод, который возвращает объект того же типа, что и объект, к которому он вызывается. Эта проблема не полностью решена динамическим возвратом, потому что она обходит статическую проверку типов, но, по крайней мере, вам не нужно все время использовать некрасивые приведения, как при использовании plain object
. Иначе говоря, приведения становятся неявными.
public class A
{
// attributes and constructor here
public virtual dynamic Clone()
{
var clone = new A();
// Do more cloning stuff here
return clone;
}
}
public class B : A
{
// more attributes and constructor here
public override dynamic Clone()
{
var clone = new B();
// Do more cloning stuff here
return clone;
}
}
public class Program
{
public static void Main()
{
A a = new A().Clone(); // No cast needed here
B b = new B().Clone(); // and here
// do more stuff with a and b
}
}