Для чего используется «динамический» тип в C # 4.0?


236

В C # 4.0 появился новый тип под названием «динамический». Все это звучит хорошо, но зачем программисту это использовать?

Есть ли ситуация, когда это может спасти день?


4
возможный дубликат stackoverflow.com/questions/2255982/…
Jörg W Mittag

Это полезно при работе с COM или динамически типизированными языками. Например, если вы используете lua или python для написания скриптов на вашем языке, очень удобно просто вызывать код скриптинга, как если бы это был обычный код.
CodesInChaos


Я надеюсь, что в этой статье есть полный ответ на ваш вопрос visualstudiomagazine.com/Articles/2011/02/01/…
разработчик

Ответы:


196

Ключевое слово 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


97
Лично мне не нравится мысль об использовании dynamicin c # для решения проблем, которые могут быть решены (возможно, даже лучше) стандартными функциями c # и статической типизацией, или, самое большее, с помощью inference типа ( var). dynamicследует использовать только когда речь идет о проблемах взаимодействия с DLR. Если вы пишете код на статическом типизированном языке, таком как c # is, то делайте это и не эмулируйте динамический язык. Это просто безобразно.
Филипп Добмайер

40
Если вы интенсивно используете dynamicпеременные в своем коде там, где они вам не нужны (как в вашем примере с квадратным корнем), вы прекращаете чистую проверку ошибок времени компиляции; вместо этого вы теперь получаете возможные ошибки во время выполнения.
Филипп Добмайер

33
В основном нормально, но пара мелких ошибок. Во-первых, неверно говорить, что динамический означает, что тип переменной может измениться. Рассматриваемая переменная имеет тип «динамический» (с точки зрения языка C #; с точки зрения CLR переменная имеет тип объекта). Тип переменной никогда не меняется. Тип времени выполнения значения переменной может быть любым типом, совместимым с типом переменной. (Или в случае ссылочных типов это может быть нулевым.)
Эрик Липперт

15
Что касается вашего второго замечания: в C # уже есть функция «создать переменную, в которую вы можете поместить что угодно» - вы всегда можете создать переменную типа object. Интересная вещь о динамике - это то, что вы указали в первом абзаце: динамический почти идентичен объекту, за исключением того, что семантический анализ откладывается до времени выполнения, а семантический анализ выполняется для типа выражения выражения во время выполнения. (В основном. Есть некоторые исключения.)
Эрик Липперт

18
Я потратил на это отрицательное мнение, главным образом потому, что он косвенно выступает за использование ключевого слова для общего пользования. У него есть целенаправленная цель (она отлично описана в ответе Ласса), и хотя этот ответ технически правильный, он, вероятно, приведет разработчиков в заблуждение.
Восьмибитный Гуру

211

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 стало проще работать, введя:

  1. Необязательные аргументы
  2. Делать refнеобязательным для COM API
  3. Именованные аргументы

Новый синтаксис для вышеуказанного вызова будет:

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 состоит из двух вещей:

  1. Новое ключевое слово в C #, dynamic
  2. Набор классов времени выполнения, которые знают, как обращаться с различными типами объектов, реализуют определенный API, который 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 для получения дополнительной информации:


12
Это также полезно помимо COM для веб-API JSON, где структура десериализованных объектов JSON не указана в C #. Например, метод Decode System.Web.Helpers.Json возвращает динамический объект .
Дамблдад

Отдельно о «они все еще считают C # строго типизированным языком»: Эрик Липперт не является поклонником «строго типизированного» описания.
Эндрю Китон

Я не согласен с ним, но это вопрос мнения, а не факта. «Строго типизированный» для меня означает, что компилятор знает, во время компиляции, какой тип используется, и, таким образом, обеспечивает соблюдение правил, установленных для этих типов. Тот факт, что вы можете выбрать динамический тип, который откладывает проверку правил и привязку к среде выполнения, для меня не означает, что язык слабо типизирован. Я обычно не сравниваю строго типизированный со слабо типизированным, однако я обычно сравниваю его с динамически типизированным, например, с такими языками, как Python, где все является уткой, пока не лает.
Лассе В. Карлсен

Какой смысл этого ответа? Половина этого относится к необязательным параметрам и интерфейсу IDispatch.
Xam

Вот почему dynamicбыло добавлено, чтобы поддержать другие экосистемы в том, как может быть выполнен вызов метода, подобного отражению, а также обеспечить своего рода подход черного ящика к структурам данных с документированным способом достижения этого.
Лассе В. Карлсен

29

Я удивлен, что никто не упомянул многократную рассылку . Обычный способ обойти это через шаблон посетителя, и это не всегда возможно, так что вы в конечном итоге с накоплением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объекты бесполезными специфическими атрибутами сериализации.


Не знал об этом случае использования. Хотя немного в лучшем случае. Это отбросит любой статический анализатор.
Кугель

2
@ Кугель, это правда, но я бы не назвал это хаком . Статический анализ - это хорошо, но я бы не позволил ему помешать изящному решению, где есть альтернативы: нарушение принципа открытого-закрытого типа (шаблон посетителя) или повышенная цикломатическая сложность, когда страшно isсложены один поверх другого.
Стелиос Адамантидис

4
Ну, у вас есть возможность сопоставления с шаблоном в C # 7, нет?
Кугель

2
Что ж, операторы намного дешевле (без двойного приведения), и вы получаете статический анализ ;-) и производительность.
Кугель

@idbrii, пожалуйста, не меняй мои ответы. Не стесняйтесь оставлять комментарии, и я уточню (при необходимости), как я все еще активен в этом сообществе. Кроме того, пожалуйста, не используйте magic; нет такой вещи как магия.
Стелиос Адамантидис

11

Для статических типизированных языков (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 для более подробной информации.


1
И изменение виртуальной машины, необходимой для динамического, на самом деле делает динамические языки проще
Dykam

2
@Dykam: нет никаких изменений в виртуальной машине. DLR прекрасно работает вплоть до .NET 2.0.
Йорг Миттаг,

@ Йорг, да, есть изменения. DLR частично переписан, потому что теперь виртуальная машина имеет встроенную поддержку динамического разрешения.
Dykam

Я был слишком оптимистичен, исследования показали, что изменения не такие большие.
Dykam

4

Пример использования:

Вы потребляете много классов, у которых есть общее свойство 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;
    }

7
Утка набирает, приятно. Однако вы должны использовать интерфейс для этого, если это ваши типы.
Кугель,

3

COM-взаимодействие Особенно неизвестно. Он был разработан специально для этого.


2

Он будет в основном использоваться жертвами RAD и Python для разрушения качества кода, IntelliSense и обнаружения ошибок времени компиляции.


Циничный ответ, но легко слишком верный. Я видел, как это делается просто для того, чтобы избежать объявления структур, в результате чего код работает, если все хорошо, но создает непредсказуемые последствия для своего стека, как только вы перемещаете его сыр.
AnthonyVO

Да, вы увидите эту классическую угловую раскрой со множеством других языковых функций. Неудивительно, что вы также увидите это здесь.
Hawkeye4040

1

Он оценивает во время выполнения, так что вы можете переключать тип, как вы можете в JavaScript, на что угодно. Это законно

dynamic i = 12;
i = "text";

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


7
Я бы не решился сказать, что это «законно». Он наверняка скомпилируется, поэтому он является «законным кодом» в том смысле, что компилятор теперь скомпилирует его, а среда выполнения запустит его. Но я бы никогда не хотел видеть этот конкретный кусок кода (или что-то похожее на него) в каком-либо коде, который я поддерживаю, или это было бы почти увольнением.
Лассе В. Карлсен

6
Конечно, но это было бы "законно" с "объектом" вместо "динамического". Вы не показали ничего интересного о динамике здесь.
Эрик Липперт

Для объекта вам придется привести его к соответствующему типу, чтобы фактически вызвать любой из его методов ... вы потеряете сигнатуру; Вы можете сделать так, чтобы ваш код вызывал любой метод без ошибок компиляции, и он выдает ошибки во время выполнения. Спешил набрать, извините, что не уточнил. И @Lasse, я бы согласился, и я, вероятно, не буду часто использовать динамику.
Брайан Майнс

1
В крайнем случае вариант использования не объяснен
denfromufa

1

Лучший вариант использования переменных типа «динамический» для меня был, когда недавно я писал слой доступа к данным в 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;
        }

0
  1. Вы можете вызывать динамические языки, такие как CPython, используя pythonnet:

dynamic np = Py.Import("numpy")

  1. Вы можете использовать дженерики dynamicпри применении к ним числовых операторов. Это обеспечивает безопасность типов и позволяет избежать ограничений генериков. Это по сути * утка набирает:

T y = x * (dynamic)x, где typeof(x) is T


0

Другой вариант использования для 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
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.