Do czego służy Typ 'dynamiczny' w C # 4.0?

C# 4.0 wprowadził nowy typ o nazwie 'dynamic'. Wszystko brzmi dobrze, ale do czego programista by go użył?

Jest sytuacja, w której może uratować sytuację?

Author: Peter Mortensen, 2010-04-22

10 answers

Słowo kluczowe dynamic jest nowe w C# 4.0 i służy do informowania kompilatora, że typ zmiennej może się zmienić lub że nie jest znany do czasu uruchomienia. Pomyśl o tym, że jest w stanie wchodzić w interakcje z obiektem bez konieczności rzucania go.

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

Uwaga nie musieliśmy rzucać ani deklarować klienta jako klienta typu. Ponieważ uznaliśmy ją za dynamiczną, runtime przejmuje kontrolę, a następnie wyszukuje i ustawia dla nas właściwość FirstName. Teraz, oczywiście, gdy używasz zmiennej dynamicznej, dajesz sprawdzanie typu kompilatora. Oznacza to wezwanie cust.MissingMethod () skompiluje się i nie zawiedzie do czasu uruchomienia. Wynikiem tej operacji jest RuntimeBinderException, ponieważ MissingMethod nie jest zdefiniowany w klasie klienta.

Powyższy przykład pokazuje jak dynamic działa podczas wywoływania metod i właściwości. Kolejną potężną (i potencjalnie niebezpieczną) funkcją jest możliwość ponownego użycia zmiennych dla różnych typów danych. Jestem pewien, że programiści Pythona, Ruby i Perla mogą myśleć z miliona sposobów, aby to wykorzystać, ale używam C# tak długo, że po prostu czuje się " źle " dla mnie.

dynamic foo = 123;
foo = "bar";

OK, więc najprawdopodobniej nie będziesz pisał kodu tak często jak powyższy. Mogą się jednak zdarzyć sytuacje, w których ponowne użycie zmiennej może się przydać lub wyczyścić brudny fragment kodu starszego typu. Jeden prosty przypadek, na który często wpadam, to ciągłe rzucanie między dziesiętnym a podwójnym.

decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");

Druga linia nie kompiluje się, ponieważ 2.5 jest wpisywane jako podwójne, a linia 3 nie kompiluje, ponieważ matematyka.Sqrt oczekuje podwójnego. Oczywiście, wszystko, co musisz zrobić, to rzucić i / lub zmienić typ zmiennej, ale mogą być sytuacje, w których dynamika ma sens.

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");

Czytaj więcej: http://www.codeproject.com/KB/cs/CSharp4Features.aspx

 206
Author: Pranay Rana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-22 12:20:15

Słowo kluczowe dynamic zostało dodane, wraz z wieloma innymi nowymi funkcjami C # 4.0, aby ułatwić rozmowę z kodem, który mieszka lub pochodzi z innych środowisk, które mają różne API.

Weź przykład.

Jeśli masz obiekt COM, taki jak obiekt Word.Application i chcesz otworzyć dokument, metoda do tego ma nie mniej niż 15 parametrów, z których większość jest opcjonalna.

Aby wywołać tę metodę, potrzebujesz czegoś takiego (upraszczam, to nie jest kod rzeczywisty):

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

Zwróć uwagę na te wszystkie argumenty? Musisz je przekazać, ponieważ C# przed wersją 4.0 nie miało pojęcia o opcjonalnych argumentach. W C# 4.0, COM API zostały ułatwione przez wprowadzenie:

  1. argumenty opcjonalne
  2. Tworzenie ref opcjonalnych dla interfejsów API COM
  3. nazwane argumenty

Nowa składnia powyższego wywołania będzie brzmiała:

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);

Zobacz, o ile łatwiej to wygląda, o ile bardziej czytelne staje się?

Rozwalmy to:

                                    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

Magia polega na tym, że kompilator C# będzie teraz wstrzykiwał potrzebny kod i pracował z nowymi klasami w środowisku runtime, aby zrobić prawie dokładnie to samo, co wcześniej, ale składnia została przed tobą ukryta, teraz możesz skupić się na co , a nie tyle na Jak. Anders Hejlsberg lubi mówić, że trzeba powoływać się na różne "zaklęcia" , co jest rodzajem Kalambury na magię całej sprawy, gdzie zazwyczaj trzeba machać ręką(s) i powiedzieć kilka magicznych słów w odpowiedniej kolejności, aby uzyskać pewien rodzaj zaklęcia dzieje. Stary sposób mówienia przez API do obiektów COM było tego dużo, trzeba było przeskoczyć przez wiele obręczy, aby nakłonić kompilator do skompilowania kodu dla Ciebie.

Rzeczy psują się w C# przed wersją 4.0 jeszcze bardziej, jeśli próbujesz rozmawiać z obiektem COM, do którego nie masz interfejsu ani klasy, wszystko, co masz, to odniesienie IDispatch.

Jeśli Nie wiem co to jest, {[9] } jest w zasadzie odbiciem dla obiektów COM. Za pomocą interfejsu IDispatch można zapytać obiekt "jaki jest numer id dla metody znanej jako Save", zbudować tablice określonego typu zawierające wartości argumentów, a na koniec wywołać metodę Invoke w interfejsie IDispatch, aby wywołać metodę, przekazując wszystkie informacje, które udało się zebrać razem.

Powyższa metoda zapisu może wyglądać tak (to zdecydowanie nie jest właściwe kod):

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, ...);
To wszystko za samo otwarcie dokumentu.

VB miał opcjonalne argumenty i wsparcie dla większości z tego wyjętego z pudełka dawno temu, więc ten kod C#:

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);

Jest w zasadzie tylko C# dogania VB pod względem ekspresji, ale robi to we właściwy sposób, czyniąc go rozszerzalnym, a nie tylko dla COM. Oczywiście jest to również dostępne dla VB.NET lub jakikolwiek inny język zbudowany na bazie środowiska.NET.

Więcej informacji na temat IDispatch Interfejs na Wikipedia: IDispatch jeśli chcesz przeczytać więcej na ten temat. To naprawdę krwawa sprawa.

Jednakże, co jeśli chcesz porozmawiać z obiektem Pythona? Istnieje do tego Inne API niż używane w obiektach COM, a ponieważ obiekty Pythona mają również charakter dynamiczny, musisz uciekać się do magii refleksji, aby znaleźć odpowiednie metody do wywołania, ich parametry itp. ale nie odbicie. NET, coś napisanego dla Pythona, prawie jak kod IDispatch powyżej, zupełnie inaczej.

A Dla Ruby? Jeszcze inne API.

JavaScript? Ta sama umowa, inne API również do tego.

Słowo kluczowe dynamic składa się z dwóch rzeczy:

  1. nowe słowo kluczowe w C#, dynamic
  2. zestaw klas runtime, które wiedzą, jak radzić sobie z różnymi typami obiektów, które implementują określone API, którego wymaga słowo kluczowe dynamic, i mapują wywołania we właściwy sposób. API jest nawet udokumentowane, więc jeśli masz obiekty pochodzące z środowiska uruchomieniowego, które nie są objęte ochroną, możesz je dodać.

Słowo kluczowe dynamic nie ma jednak zastąpić istniejącego kodu. NET. Oczywiście, możesz to zrobić, ale nie zostało to dodane z tego powodu, a autorzy języka programowania C# z Andersem Hejlsbergiem na czele, byli najbardziej nieugięci, że nadal uważają C# za silnie napisany język i nie poświęcą tej zasady.

Oznacza to, że chociaż możesz napisać kod w ten sposób:

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

I mieć go skompilować, nie był to rodzaj magicznego-pozwala-dowiedzieć się, co-masz-na myśli-w-runtime typu systemu.

Celem było ułatwienie rozmowy z innymi typami obiektów.

W Internecie jest mnóstwo materiałów na temat słowa kluczowego, zwolenników, przeciwników, dyskusji, narzekań, pochwał itp.

Proponuję zacząć od poniższych linków, a następnie google dla więcej:

 217
Author: Lasse V. Karlsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-05 13:46:01

Dziwię się, że nikt nie wspomniał o wielokrotnej wysyłce. Zwykle można to obejść za pomocą Visitor pattern i nie zawsze jest to możliwe, więc kończy się to sprawdzeniem is.

Oto przykład mojego zastosowania. Zamiast robić:
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());
}

Robisz:

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

Zauważ, że w pierwszym przypadku ElevationPoint jest podklasą MapPoint i jeśli nie jest umieszczona przed MapPointnigdy nie zostanie osiągnięta. Tak nie jest z dynamicznym, ponieważ zostanie wywołana najbliższa metoda dopasowania.

Jak można się domyślić z kodu, ta funkcja przydała się podczas wykonywania tłumaczenia z obiektów ChartItem do ich wersji serializowalnych. Nie chciałem zanieczyszczać kodu odwiedzającym i nie chciałem również zanieczyszczać moich obiektów ChartItem bezużytecznymi atrybutami specyficznymi dla serializacji.

 31
Author: Stelios Adamantidis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-11 17:04:09

Ułatwia współpracę statycznych języków typowanych (CLR) z dynamicznymi (python, ruby ...) uruchomiony na DLR (dynamic language runtime), patrz MSDN :

Na przykład, możesz użyć poniższego kodu, aby zwiększyć licznik w XML w C#.

Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);

Używając DLR, możesz użyć poniższego kodu zamiast ta sama operacja.

scriptobj.Count += 1;

MSDN wymienia te zalety:

  • Upraszcza Przenoszenie Dynamicznego Języki do. NET Framework
  • włącza funkcje dynamiczne w językach typowanych statycznie
  • [16]}zapewnia przyszłe korzyści z DLR i. NET Framework
  • umożliwia współdzielenie bibliotek i obiektów
  • zapewnia szybką dynamiczną wysyłkę i wywoływanie

Zobacz MSDN Po Więcej Szczegółów.

 11
Author: Philip Daubmeier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-04 16:53:43

Przykład użycia:

Używasz wielu klas, które mają właściwość comun '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; }

}

Jeśli napiszesz metodę commun, która pobiera wartość właściwości 'CreationDate', będziesz musiał użyć reflection:

    static DateTime RetrieveValueOfCreationDate(Object item)
    {
        return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
    }

Z koncepcją 'dynamiczną', Twój kod jest o wiele bardziej elegancki :

    static DateTime RetrieveValueOfCreationDate(dynamic item)
    {
        return item.CreationDate;
    }
 6
Author: Akli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-09 18:38:12

COM interop. Zwłaszcza Nie Wiem. Został zaprojektowany specjalnie dla niego.

 3
Author: Aen Sidhe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-22 12:16:24

Najlepszym przypadkiem użycia zmiennych typu 'dynamicznego' było dla mnie, gdy ostatnio pisałem warstwę dostępu do danych w ADO.NET (using SqlDataReader) i Kod wywoływał już napisane starsze procedury składowane. Istnieją setki tych starszych procedur składowanych zawierających większość logiki biznesowej. Moja warstwa dostępu do danych musiała zwrócić jakieś ustrukturyzowane dane do warstwy logiki biznesowej, opartej na C#, aby wykonać pewne manipulacje (, chociaż prawie ich nie ma ). Każda procedura składowana zwraca inny zestaw danych ( kolumny tabeli ). Więc zamiast tworzyć dziesiątki klas lub struktur do przechowywania zwracanych danych i przekazywania ich do BLL, napisałem poniższy kod, który wygląda dość elegancko i schludnie.

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;
        }
 3
Author: user1451111,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-28 06:50:38

Jest ewaluowany w czasie wykonywania, więc możesz zmienić typ tak jak w JavaScript na dowolny. To jest legit:

dynamic i = 12;
i = "text";

I tak możesz zmienić typ, jak potrzebujesz. Użyj go w ostateczności; jest korzystny, ale słyszałem, że wiele dzieje się pod scenami pod względem generowanego IL i może to przynieść cenę wydajności.

 1
Author: Brian Mains,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-04 03:58:49
  1. możesz wywołać w językach dynamicznych, takich jak CPython, używając pythonnet:

dynamic np = Py.Import("numpy")

  1. możesz przypisać generyki do dynamic, stosując na nich operatory numeryczne. Zapewnia to bezpieczeństwo typu i unika ograniczeń leków generycznych. Jest to w istocie * typowanie kaczki:

T y = x * (dynamic)x, Gdzie typeof(x) is T

 0
Author: denfromufa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-06 06:37:13

Inny przypadek użycia dynamic typowania dotyczy metod wirtualnych, które doświadczają problemu z kowariancją lub kontrawariancją. Jednym z takich przykładów jest metoda infamous Clone, która zwraca obiekt tego samego typu co obiekt, na którym jest wywoływany. Ten problem nie jest całkowicie rozwiązany z dynamicznym zwrotem, ponieważ pomija sprawdzanie typu statycznego, ale przynajmniej nie musisz używać brzydkich odlewów przez cały czas, jak za pomocą zwykłego object. Inaczej mówiąc, odlewy stają się niejawne.

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
Author: Frederic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-28 13:18:36