Для чого використовується тип "динамічний" у 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

Динамічне ключове слово є новим для C # 4.0 і використовується для того, щоб сказати компілятору, що тип змінної може змінюватися або що вона не відома до часу виконання. Подумайте про це як про можливість взаємодіяти з Об'єктом без необхідності його кидати.

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

Зверніть увагу, що нам не потрібно було видавати і не заявляти про службу як клієнта типу. Оскільки ми оголосили це динамічним, час виконання бере на себе, а потім шукає та встановлює для нас властивість FirstName. Тепер, звичайно, коли ви використовуєте динамічну змінну, ви відмовляєтесь від перевірки типу компілятора. Це означає, що виклик cust.MissingMethod () буде компілюватися та не виходити з ладу до часу виконання. Результатом цієї операції є RuntimeBinderException, оскільки MissingMethod не визначений у класі клієнта.

Наведений вище приклад показує, як працює динаміка під час виклику методів та властивостей. Ще одна потужна (і потенційно небезпечна) функція - це можливість повторного використання змінних для різних типів даних. Я впевнений, що програмісти 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 не компілюється, оскільки Math.Sqrt очікує подвійного. Очевидно, що все, що вам потрібно зробити, - це передавання та / або зміна типу змінної, але можуть бути ситуації, коли динамічні сенси використовувати.

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


96
Особисто мені не подобається думка про використання dynamicc в для вирішення завдань, які можна вирішити (можливо, навіть краще) за допомогою стандартних функцій c # та статичного введення тексту, або, принаймні, з висновком типу ( var). dynamicслід використовувати лише тоді, коли мова йде про проблеми взаємодії з DLR. Якщо ви пишете код на мові статичного типу, як, наприклад, c # є, зробіть це, і не імітуйте динамічну мову. Це просто потворно.
Філіп Добмайєр

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

33
Переважно добре, але пара незначних помилок. По-перше, неправильно сказати, що динамічні засоби можуть змінювати тип змінної . Розглянута змінна має тип "динамічний" (з точки зору мови C #; з точки зору CLR змінна є об'єктом типу). Тип змінної ніколи не змінюється. Тип виконання значення змінної може бути будь-якого типу, сумісного з типом змінної. (Або у випадку еталонних типів, це може бути нульовим.)
Ерік Ліпперт

15
Стосовно вашого другого пункту: C # вже мав особливість "зробити змінну, в яку можна помістити що завгодно" - ви завжди можете зробити змінну об'єкта типу. Цікавим у динаміці є те, що ви вказуєте у своєму першому абзаці: динаміка є майже ідентичною об’єкту, за винятком того, що семантичний аналіз відкладається до часу виконання, а семантичний аналіз проводиться за типом виконання виразу. (Переважно. Є деякі винятки.)
Ерік Ліпперт

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додатковим для API 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інтерфейсу ви можете запитати об'єкт "який ідентифікаційний номер методу, відомого як" Зберегти ", і створити масиви певного типу, що містять значення аргументу, і нарешті викликати 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 і для цього.

Динамічне ключове слово складається з двох речей:

  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 повертає динамічний об'єкт .
dumbbledad

Осторонь про те, що "вони все ще розглядають 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
@Kugel це правда, але я б не назвав це злом . Статичний аналіз хороший, але я не дозволяв би мені заважати елегантному рішенню, де альтернативами є: порушення відкритого закритого принципу (шаблон відвідувача) або підвищена цикломатична складність із боязливим isскладанням один на інший.
Stelios Adamantidis

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

2
Добре, що оператори набагато дешевші таким чином (уникаючи подвійного виступу), і ви отримуєте назад статичний аналіз ;-) та продуктивність.
Кугель

@idbrii, будь ласка, не змінюй мої відповіді. Не соромтесь залишити коментар, і я уточню (якщо потрібно), оскільки я все ще активний у цій спільноті. Також, будь ласка, не використовуйте magic; не існує такого поняття, як магія.
Stelios Adamantidis

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
А зміна тота, яку він потрібен для динамічного віртуального моделювання, фактично спрощує динамічні мови.
Дікам

2
@Dykam: ВММ немає змін. DLR працює чудово майже на шляху до .NET 2.0.
Йорг W Міттаг

@ Йорг, так, зміни є. DLR частково переписаний, оскільки тепер він має вбудовану підтримку динамічної роздільної здатності.
Дікам

Я був занадто оптимістичним, дослідження показали, що зміни не такі великі.
Дікам

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; }

}

Якщо ви пишете метод комунікації, який отримує значення властивості '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, як ви можете в 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метод, який повертає об'єкт того ж типу, що і об'єкт, на який він викликається. Ця проблема не вирішується повністю з динамічним поверненням, оскільки вона минає перевірку статичного типу, але принаймні вам не потрібно весь час використовувати некрасиві касти при використанні звичайного 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.