CodeGym /Kursy /C# SELF /Typy dynamiczne ( dynamic

Typy dynamiczne ( dynamic) i DLR

C# SELF
Poziom 63 , Lekcja 2
Dostępny

1. Wprowadzenie

C# — przedstawiciel języków z silnym typowaniem. To znaczy, że na etapie kompilacji zmienna powinna mieć określony typ i do członków klasy można odwoływać się tylko zgodnie z tym typem. To jest bezpieczne, wygodne, czytelne dla IDE i kompilatora.

Ale czasami trzeba operować na obiektach, których struktura może być nieznana na etapie kompilacji — na przykład, gdy:

    IronPython lub IronRuby), gdzie typowanie jest dynamiczne.
  • Przy pracy z obiektami COM (np. z Microsoft Office przez Interop).
  • Przy współpracy z dynamicznymi strukturami danych: na przykład z mało sformalizowanymi obiektami JSON.
  • Nie chcemy pisać sterty szablonowego kodu, łatwiej „ufając” obiektowi na słowo i próbując wywołać potrzebną metodę po nazwie.

Z pojawieniem się w C# 4.0 słowa kluczowego dynamic i platformy DLR pojawiła się możliwość pisania kodu, który zachowuje się „jak w Pythonie, tylko w C#”: weryfikacja odbywa się w czasie wykonania, a nie podczas kompilacji. Dzięki temu magicznemu słowu można sprawić, żeby kompilator był mniej rygorystyczny i ręcznie sprawdzić, czy jest dana metoda/właściwość dopiero gdy program naprawdę do tego miejsca dotrze.

Co to jest DLR?

Dynamic Language Runtime (DLR) — to infrastrukturalne rozszerzenie CLR (Common Language Runtime), które pozwala na implementację wsparcia dla języków dynamicznych (i dynamicznych zachowań w językach statycznych). DLR zajmuje się wykonywaniem operacji, których kompilator nie może sprawdzić: na przykład wywołanie metody po nazwie, jeśli typ będzie znany dopiero w czasie działania programu. Dzięki DLR C# dostaje „tryb dynamiczny”.

2. Słowo kluczowe dynamic: jak to działa

Różnica względem object

Niektórzy początkujący myślą, że dynamic to po prostu modniejszy sposób napisania object i ciągłego rzutowania. To nieprawda.

  • Zmienne typu object zawsze wymagają jawnego rzutowania do konkretnego typu, żeby wywołać metody (i kompilator to sprawdza).
  • Zmienne typu dynamic dają kompilatorowi polecenie: „uwierz mi, sam wiem, jaki tam będzie typ — sprawdzimy to w runtime!”. Jeśli metoda nie istnieje — będzie wyjątek w czasie wykonania.

Przykład:


// Przykład z object
object obj = "Hello, C#!";
// obj.ToUpper(); // Błąd kompilacji! Kompilator nie wie, że obj to string
string s = ((string)obj).ToUpper(); // Tylko tak

// Przykład z dynamic
dynamic dyn = "Hello, C#!";
string upper = dyn.ToUpper(); // Kompilator nie protestuje, weryfikacja będzie w runtime

Z dynamic można odwoływać się do nieistniejących właściwości i metod. Jeśli ich nie ma — zostanie rzucony wyjątek typu RuntimeBinderException.

Użycie w przykładzie aplikacji

Załóżmy, że w naszym mini-aplikacji jest moduł pracy z danymi JSON, których struktura może się zmieniać w locie. Z dynamic można odwoływać się do właściwości, jakby one zawsze istniały:


dynamic user = new System.Dynamic.ExpandoObject();
user.Name = "Wiktor";
user.Age = 35;
Console.WriteLine($"Imię: {user.Name}, wiek: {user.Age}");

3. DLR w akcji: co się dzieje „pod maską”

Gdy zmienna zadeklarowana jest jako dynamic, kompilator C# zapamiętuje, że wszelkie odwołania do metod, właściwości, indeksatorów, operatorów itp. dla tego obiektu nie da się sprawdzić z wyprzedzeniem.

Zamiast tego w miejscu każdego dynamicznego wywołania wstawiany jest kod odwołania do systemowego „wolnego dispatcher'a”. DLR obserwuje: czy faktycznie istnieje ta metoda lub właściwość, do której zwracamy się dość nonszalancko, z optymizmem? Jeśli tak — wykonuje wywołanie, jeśli nie — rzuca wyjątek.

Schemat wizualny


+------------------------+
| Kod z dynamic          |
+----------+-------------+
           |
           v
+----------+-------------+
| Kompilator C# wstawia  |
| "zapytanie do DLR"     |
+----------+-------------+
           |
           v
+----------+-------------+
| W runtime DLR szuka    |
| metody/właściwości     |
+----------+-------------+
           |
      /---------\
      |         |
   znaleziono  nie znaleziono
      |         |
      v         v
  wywołanie   wyjątek
   metody     (RuntimeBinderException)

DLR dba o wyszukiwanie metod i właściwości w obiekcie — i nawet cache'uje często używane dynamiczne wywołania!

Trochę historii

DLR został stworzony dla wsparcia języków pierwotnie dynamicznych: IronPython, IronRuby, oraz dla ułatwienia integracji z dynamicznymi obiektami w C#. Dziś jest aktywnie używany przy pracy z obiektami typu ExpandoObject, DynamicObject, a także z COM i dynamicznie serializowanymi danymi.

4. Porównanie: static vs dynamic

Porównajmy, jak wyglądają wywołania właściwości i metod w paradygmacie statycznym i dynamicznym:

Sposób Deklaracja Sprawdzenie podczas kompilacji Zachowanie przy błędzie
object
object obj = ...
Wymaga castu do odpowiedniego typu Błąd kompilacji lub InvalidCastException
dynamic
dynamic dyn = ...
Nie, wszystko w runtime RuntimeBinderException, jeśli metoda nie istnieje
typ statyczny
MyClass a = ...
Sprawdzane przez kompilator Błąd kompilacji

Przykład dla przejrzystości:


// Statyczne typowanie
MyUser u = new MyUser();
u.PrintInfo(); // Sprawdzane przez kompilator

// dynamic
dynamic du = new MyUser();
du.PrintInfo(); // Kompilator nie sprawdza, znajdzie metodę dopiero w runtime

// object
object ou = new MyUser();
// ou.PrintInfo(); // Błąd kompilacji
((MyUser)ou).PrintInfo(); // Trzeba jawnie rzutować do typu

Metody statyczne i dynamic


dynamic d = "Hello";
Console.WriteLine(d.Length); // 5 — wszystko OK

Console.WriteLine(d.FakeMethod()); // RuntimeBinderException w czasie wykonania

Ale trzeba być ostrożnym! Jeśli się przegapisz albo nazwa metody nie istnieje — ujawni się to dopiero podczas działania aplikacji. Klasyczny przykład „błędu na produkcji”.

5. Praktyczne przykłady: dynamika w rzeczywistych zadaniach

Praca z ExpandoObject


using System.Dynamic;

dynamic person = new ExpandoObject();
person.Name = "Stephen";
person.Greet = (Action)(() => Console.WriteLine($"Cześć, {person.Name}!"));

person.Greet(); // Wypisze: Cześć, Stephen!

Tutaj w locie dodaliśmy właściwość Name i nawet delegat Greet. Bardzo podobne do pracy z obiektami w JavaScript.

Mapowanie do struktur JSON


using System.Dynamic;
using Newtonsoft.Json;

string json = @"{""name"": ""Andy"", ""age"": 30}";
dynamic obj = JsonConvert.DeserializeObject
  
   (json); Console.WriteLine(obj.name); // Andy Console.WriteLine(obj.age); // 30 
  

Zwróć uwagę: jeśli napiszesz obj.surname — będzie błąd już podczas wykonania.

COM-Interop (automatyzacja Office)


dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true; // „Widzimy” właściwość, chociaż kompilator nie zna jej typu

6. Przydatne niuanse

DLR, dynamic i użycie z innymi językami .NET

  • IronPython/IronRuby: można instancjonować obiekty tych języków i wywoływać ich metody z C# przez dynamic.
  • Obiekty COM: automatyczne opakowanie metod i właściwości przez dynamic.
  • Własne dynamiczne obiekty: implementując interfejs IDynamicMetaObjectProvider, możesz zrobić własną klasę z dynamicznymi wywołaniami.

Przykład integracji z IronPython


using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

ScriptEngine engine = Python.CreateEngine();
dynamic py = engine.Execute("class Test: pass\nTest()");
py.x = 42;
Console.WriteLine(py.x); // 42

Jak tworzy się własne typy dynamiczne

Zwykle łatwiej jest dziedziczyć po DynamicObject.


using System.Dynamic;

class MyDynamic : DynamicObject
{
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (binder.Name == "Ping")
        {
            result = "Pong!";
            return true;
        }
        result = null;
        return false;
    }
}

dynamic d = new MyDynamic();
Console.WriteLine(d.Ping); // Pong!
Console.WriteLine(d.Miss); // null (nie rzuca wyjątku)

Kiedy dynamic jest szkodliwy

  • Zmniejsza wydajność (wywołania dynamiczne są wolniejsze od statycznych).
  • Utrudnia debugowanie i utrzymanie kodu.
  • Odbiera większość „fajnych rzeczy” z IDE: autouzupełnianie, refaktoryzację, wyszukiwanie użyć.
  • Może prowadzić do błędów wykrywanych dopiero u użytkownika lub na serwerze.

Kiedy warto naprawdę używać dynamic

  • Integracja z zewnętrznymi bibliotekami, gdzie niemożliwe lub nieopłacalne jest pisanie wrapperów (COM, Python, JavaScript).
  • Operacje na słabo typowanych strukturach (JSON/XML/ExpandoObject).
  • Do pisania frameworków, metaprogramowania i elastycznych API, które mają być z założenia dynamiczne.

We wszystkich innych przypadkach lepiej używać typów statycznych i cieszyć się pomocą kompilatora :-)

7. Typowe błędy przy pracy z dynamic i DLR

Błąd nr 1: Odwołanie do nieistniejących członków.
Wywołanie nieistniejącej metody lub właściwości dla dynamic prowadzi do RuntimeBinderException w runtime, co jest trudne do wykrycia podczas rozwoju.

Błąd nr 2: Ignorowanie sprawdzeń typów.
Bez sprawdzenia typu przed wywołaniem (np. if (obj is IDictionary<string, object>)) możesz dostać niespodziewane błędy w runtime.

Błąd nr 3: Używanie dynamic w publicznych API.
Typy dynamiczne komplikują utrzymanie kodu, bo IDE i analizatory nie mogą zasugerować poprawnych członków, co obniża czytelność.

Błąd nr 4: Nadużywanie dynamic w wydajnym kodzie.
Wywołania dynamiczne są wolniejsze od statycznych i zwiększają narzut. Używaj dynamic tylko gdy to konieczne.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION