Для чего используется «динамический» тип в C # 4.0? - PullRequest
204 голосов
/ 22 апреля 2010

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

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

Ответы [ 11 ]

194 голосов
/ 22 апреля 2010

Ключевое слово dynamic было добавлено вместе со многими другими новыми функциями C # 4.0, чтобы упростить общение с кодом, который существует в других средах выполнения или происходит из него, с другими API.

Takeпример.

Если у вас есть 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 с API-интерфейсами COM стало проще работать, введя:

  1. Необязательные аргументы
  2. Включение ref необязательно для COM-интерфейсов
  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, просто совсем другое.

А для Ruby?Другой API все еще.

JavaScript?То же самое, другой API для этого тоже.

Динамическое ключевое слово состоит из двух вещей:

  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 для получения дополнительной информации:

163 голосов
/ 22 апреля 2010

Динамическое ключевое слово является новым для 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

22 голосов
/ 15 ноября 2016

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

11 голосов
/ 22 апреля 2010

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

4 голосов
/ 09 апреля 2017

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

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

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

1 голос
/ 28 марта 2018

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

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

0 голосов
/ 28 февраля 2019

Другой вариант использования для ввода dynamic предназначен для виртуальных методов, которые испытывают проблемы с ковариацией или контравариантностью. Одним из таких примеров является печально известный метод Clone, который возвращает объект того же типа, что и объект, для которого он вызывается. Эта проблема не полностью решена с динамическим возвратом, потому что он обходит статическую проверку типов, но, по крайней мере, вам не нужно все время использовать некрасивое приведение, как при использовании простого 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
    }
}
0 голосов
/ 04 марта 2016
  1. Вы можете вызывать динамические языки, такие как CPython, используя pythonnet:

dynamic np = Py.Import("numpy")

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

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

...