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


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

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

10 173

10 ответов:

динамическое ключевое слово является новым для C# 4.0 и используется, чтобы сообщить компилятору, что тип переменной может измениться или что он не известен до времени выполнения. Подумайте об этом, как о возможности взаимодействовать с объектом, не бросая его.

dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!

обратите внимание, что нам не нужно было ни бросать, ни объявлять cust как тип клиента. Поскольку мы объявили его динамическим, среда выполнения берет на себя, а затем выполняет поиск и устанавливает для нас свойство FirstName. Теперь, конечно, когда вы используете динамическую переменную, вы отказываемся от проверки типа компилятора. Это означает, что вызов абон.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 набирается как двойной и строка 3 не компилируется, потому что математика.Sqrt ожидает двойника. Очевидно, что все, что вам нужно сделать, это отлить и/или изменить тип переменной, но могут возникнуть ситуации, когда dynamic имеет смысл использовать.

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

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

брать пример.

если у вас есть 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 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 интерфейс для вызова метода, передавая всю информацию, которую вы имеете умудрились выпросить вместе.

приведенный выше метод сохранения может выглядеть так (это определенно не правильный код):

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 интерфейс Wikipedia: IDispatch если вы хотите узнать больше об этом. Это действительно кровавая штука.

однако, что делать, если вы хотите поговорить с объектом Python? Для этого есть другой API, чем тот, который используется для COM-объектов, и поскольку объекты Python также являются динамическими по своей природе, вам нужно прибегнуть к отражение магии, чтобы найти правильные методы для вызова, их параметры и т. д. но не отражение .NET, что-то написанное для Python, в значительной степени похожее на код IDispatch выше, просто совершенно другое.

а для Руби? Другой API все еще.

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

ключевое слово dynamic, состоит из двух вещей:

  1. новое ключевое слово в C#dynamic
  2. набор выполнения классы, которые знают, как работать с различными типами объектов, которые реализуют определенный API, что dynamic ключевое слово требует, и отображает вызовы на правильный способ делать вещи. API даже документирован, поэтому, если у вас есть объекты, которые поступают из среды выполнения, не охваченной, вы можете добавить его.

The dynamic ключевое слово, однако, не предназначено для замены любого существующего кода .NET-only. Конечно, ты можете сделать это, но он не был добавлен по этой причине, и авторы из языка программирования C# с Андерсом Хейлсбергом на фронте было наиболее непреклонно, что они по-прежнему считают C# строго типизированным языком и не будут жертвовать этим принципом.

это означает, что вы можете написать такой код:

dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;

и пусть он компилируется, он не был задуман как своего рода magic-lets-figure-out-what-you-meant-at-runtime тип системы.

вся цель состояла в том, чтобы облегчить разговор с другими типами объекты.

в интернете есть много материалов о ключевом слове, сторонниках, противниках, обсуждениях, разглагольствованиях, похвалах и т. д.

я предлагаю вам начать со следующих ссылок, а затем google для получения дополнительной информации:

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

как вы можете догадаться из кода, эта функция пригодилась, когда я выполнял перевод с объектов ChartItem на их сериализуемые версии. Я не хотел загрязнять мой код посетителями, и я не хотел также загрязнять мой ChartItem объекты с бесполезными атрибутами сериализации.

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

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

вы потребляете много классов, которые имеют свойство commun '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;
    }

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

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

лучший вариант использования переменных типа "динамический" для меня был, когда недавно я писал слой доступа к данным в 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. вы можете вызвать в динамических языках, таких как CPython с помощью pythonnet:

dynamic np = Py.Import("numpy")

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

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

он вычисляет во время выполнения, так что вы можете переключить тип, как вы можете в JavaScript, чтобы все, что вы хотите. Это законно:

dynamic i = 12;
i = "text";

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