W C # 4.0 wprowadzono nowy typ o nazwie „dynamiczny”. Wszystko brzmi dobrze, ale do czego programista by to wykorzystał?
Czy jest sytuacja, w której może uratować dzień?
W C # 4.0 wprowadzono nowy typ o nazwie „dynamiczny”. Wszystko brzmi dobrze, ale do czego programista by to wykorzystał?
Czy jest sytuacja, w której może uratować dzień?
Odpowiedzi:
Dynamiczne słowo kluczowe jest nowością 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 może wchodzić w interakcje z przedmiotem bez 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 przesyłać ani deklarować klienta jako klienta typu. Ponieważ zadeklarowaliśmy, że jest dynamiczny, środowisko wykonawcze przejmuje, a następnie wyszukuje i ustawia dla nas właściwość FirstName. Teraz, oczywiście, kiedy używasz zmiennej dynamicznej, rezygnujesz z sprawdzania typu kompilatora. Oznacza to, że wywołanie cust.MissingMethod () zostanie skompilowane i nie zakończy się niepowodzeniem, dopóki nie zostanie uruchomione. Wynikiem tej operacji jest RuntimeBinderException, ponieważ MissingMethod nie jest zdefiniowany w klasie Customer.
Powyższy przykład pokazuje, jak działa dynamika podczas wywoływania metod i właściwości. Kolejną potężną (i potencjalnie niebezpieczną) funkcją jest możliwość ponownego wykorzystywania zmiennych dla różnych typów danych. Jestem pewien, że programiści Python, Ruby i Perl mogą wymyślić milion sposobów na skorzystanie z tego, ale używam C # tak długo, że wydaje mi się to „złe”.
dynamic foo = 123;
foo = "bar";
OK, więc najprawdopodobniej nie będziesz pisać kodu tak jak powyżej. Może się jednak zdarzyć, że ponowne użycie zmiennych może się przydać lub wyczyścić brudny kawałek starszego kodu. Jednym prostym przypadkiem, w którym często się spotykam, jest ciągłe rzucanie między dziesiętną a podwójną.
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 jest kompilowana, ponieważ 2.5 jest wpisany jako podwójny, a linia 3 nie jest kompilowana, ponieważ Math.Sqrt oczekuje podwójnej. Oczywiście wszystko, co musisz zrobić, to rzutować i / lub zmienić typ zmiennej, ale mogą zdarzyć się sytuacje, w których użycie dynamiki 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 funkcji: http://www.codeproject.com/KB/cs/CSharp4Features.aspx
dynamic
in c # do rozwiązywania problemów, które można rozwiązać (być może nawet lepiej) za pomocą standardowych funkcji c # i pisania statycznego lub co najwyżej za pomocą wnioskowania typu ( var
). dynamic
powinien być stosowany tylko w przypadku problemów z interoperacyjnością z DLR. Jeśli piszesz kod w języku statycznym, takim jak c #, zrób to i nie emuluj języka dynamicznego. To po prostu brzydkie.
dynamic
zmiennych w swoim kodzie, gdzie ich nie potrzebujesz (jak w przykładzie z kwadratowym korzeniem), rezygnujesz z sprawdzania błędów podczas kompilacji; zamiast tego otrzymujesz możliwe błędy czasu wykonywania.
Słowo dynamic
kluczowe 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 wykonawczych, które mają różne interfejsy API.
Brać przykład.
Jeśli masz obiekt COM, taki jak Word.Application
obiekt, i chcesz otworzyć dokument, metoda wykonania tego ma co najmniej 15 parametrów, z których większość jest opcjonalna.
Aby wywołać tę metodę, potrzebujesz czegoś takiego (upraszczam, to nie jest rzeczywisty kod):
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 przekazać je, ponieważ C # przed wersją 4.0 nie zawierał opcjonalnych argumentów. W C # 4.0 interfejsy API COM zostały ułatwione dzięki wprowadzeniu:
ref
opcjonalne dla COM APINowa składnia powyższego wywołania to:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
Zobacz, o ile łatwiej to wygląda, o ile bardziej staje się czytelne?
Rozbijmy to na części:
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 # wstrzykuje teraz niezbędny kod i pracuje z nowymi klasami w środowisku wykonawczym, aby wykonać 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 tak bardzo jak . Anders Hejlsberg lubi mówić, że trzeba przywoływać różne „zaklęcia”, co jest swoistą kalamburą całej magii, w której zazwyczaj trzeba machać ręką (rękami) i wypowiadać magiczne słowa we właściwej kolejności aby uzyskać pewien rodzaj zaklęcia. Stary sposób API do komunikowania się z obiektami COM był bardzo duży, trzeba było przeskakiwać przez wiele obręczy, aby nakłonić kompilator do skompilowania kodu za ciebie.
Sprawy psują się w C # przed wersją 4.0, a nawet bardziej, jeśli próbujesz porozmawiać z obiektem COM, dla którego nie masz interfejsu lub klasy, wszystko, co masz, to IDispatch
odwołanie.
Jeśli nie wiesz, co to jest, IDispatch
jest w zasadzie odbiciem dla obiektów COM. Za pomocą IDispatch
interfejsu możesz zapytać obiekt „jaki jest numer identyfikacyjny metody znanej jako Save”, i zbudować tablice określonego typu zawierające wartości argumentów, a na końcu wywołać Invoke
metodę IDispatch
interfejsu, aby wywołać metodę, przekazując wszystkie informacje, które udało ci się zebrać razem.
Powyższa metoda Save może wyglądać następująco (to zdecydowanie nie jest właściwy 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, ...);
Wszystko po to, by otworzyć dokument.
VB miał opcjonalne argumenty i wsparcie dla większości tego od razu po wyjęciu z pudełka, więc ten kod C #:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
jest w zasadzie po prostu C # dogania VB pod względem ekspresyjności, ale robi to we właściwy sposób, umożliwiając rozszerzenie, nie tylko dla COM. Oczywiście jest to również dostępne dla VB.NET lub dowolnego innego języka zbudowanego na środowisku uruchomieniowym .NET.
Więcej informacji na temat IDispatch
interfejsu można znaleźć na Wikipedii: IDispatch, jeśli chcesz dowiedzieć się więcej na ten temat. To naprawdę krwawe rzeczy.
Co jednak, jeśli chcesz porozmawiać z obiektem Python? Istnieje inny interfejs API niż ten używany dla obiektów COM, a ponieważ obiekty Pythona również mają charakter dynamiczny, musisz skorzystać z magii odbicia, aby znaleźć odpowiednie metody wywoływania, ich parametry itp., Ale nie .NET odbicie, coś napisanego dla Pythona, zupełnie jak powyższy kod IDispatch, zupełnie inny.
A dla Ruby? Wciąż inny interfejs API.
JavaScript? Ta sama oferta, inne API również.
Dynamiczne słowo kluczowe składa się z dwóch rzeczy:
dynamic
dynamic
wymagany przez słowo kluczowe, i odwzorowuje wywołania we właściwy sposób. Interfejs API jest nawet udokumentowany, więc jeśli masz obiekty pochodzące z środowiska wykonawczego, które nie są objęte, możesz je dodać.Słowo dynamic
kluczowe nie ma jednak na celu zastąpienia żadnego istniejącego kodu tylko .NET. Jasne, możesz to zrobić, ale z tego powodu nie został dodany, a autorzy języka programowania C # z Anders Hejlsberg na czele, byli bardzo nieugięci, że nadal uważają C # za język mocno napisany na maszynie i nie poświęcą się ta zasada.
Oznacza to, że chociaż możesz napisać taki kod:
dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;
i niech to się skompiluje, nie miało to być rodzajem systemu typu magia, który wymyślił, co miałeś na myśli w czasie wykonywania.
Głównym celem było ułatwienie rozmowy z innymi typami obiektów.
W Internecie znajduje się mnóstwo materiałów na temat słowa kluczowego, zwolenników, przeciwników, dyskusji, rantów, pochwał itp.
Sugeruję zacząć od poniższych linków, a następnie google, aby uzyskać więcej:
dynamic
dodano, aby wesprzeć inne ekosystemy w tym, jak można wywoływać metody refleksyjne, a także zapewnić rodzaj czarnej skrzynki dla struktur danych z udokumentowanym sposobem osiągnięcia tego.
Dziwi mnie, że nikt nie wspomniał o wysyłce wielokrotnej . Zwykłym sposobem obejścia tego problemu jest wzorzec użytkownika i nie zawsze jest to możliwe, więc kończy się na stosach is
czeków.
Oto przykład mojego własnego 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());
}
Ty 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 ona podklasą MapPoint
i jeśli nie zostanie umieszczona wcześniej MapPoint
, nigdy nie zostanie osiągnięta. Nie dotyczy to dynamiki, ponieważ zostanie wywołana metoda najbliższego dopasowania.
Jak można się domyślić na podstawie kodu, ta funkcja przydała się, gdy wykonywałem tłumaczenie obiektów ChartItem na ich wersje możliwe do serializacji. Nie chciałem zanieczyszczać mojego kodu odwiedzającymi i nie chciałem również zanieczyszczać mojegoChartItem
obiektów bezużytecznymi atrybutami specyficznymi dla serializacji.
is
stosem jeden na drugim.
magic
; nie ma czegoś takiego jak magia.
Ułatwia współdziałanie języków statycznych (CLR) z dynamicznymi (python, ruby ...) działającymi na DLR (środowisko uruchomieniowe języka dynamicznego), patrz MSDN :
Na przykład możesz użyć następującego kodu, aby zwiększyć licznik w XML w C #.
Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);
Korzystając z DLR, możesz użyć następującego kodu zamiast do tej samej operacji.
scriptobj.Count += 1;
MSDN wymienia następujące zalety:
- Upraszcza przenoszenie języków dynamicznych do .NET Framework
- Włącza funkcje dynamiczne w językach o typie statycznym
- Zapewnia przyszłe korzyści z DLR i .NET Framework
- Umożliwia udostępnianie bibliotek i obiektów
- Zapewnia szybką dynamiczną wysyłkę i wywołanie
Aby uzyskać więcej informacji, zobacz MSDN .
Przykład zastosowania:
Zużywasz wiele klas, które mają właściwość komunalną „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ć refleksji:
static DateTime RetrieveValueOfCreationDate(Object item)
{
return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
}
Dzięki koncepcji „dynamicznej” kod jest znacznie bardziej elegancki:
static DateTime RetrieveValueOfCreationDate(dynamic item)
{
return item.CreationDate;
}
Będzie głównie używany przez ofiary RAD i Pythona do niszczenia jakości kodu, IntelliSense i wykrywania błędów kompilacji czasu.
Sprawdza się w czasie wykonywania, więc możesz zmienić typ, tak jak w JavaScript, na cokolwiek chcesz. Jest to uzasadnione:
dynamic i = 12;
i = "text";
Możesz więc zmienić typ według potrzeb. Użyj go w ostateczności; jest to korzystne, ale słyszałem, że wiele dzieje się pod scenami pod względem generowanej IL, a to może mieć cenę za wydajność.
Najlepszym przypadkiem użycia zmiennych typu „dynamicznego” było dla mnie, gdy ostatnio pisałem warstwę dostępu do danych w ADO.NET ( używając SQLDataReader ), a kod wywoływał już zapisane procedury składowane. Istnieją setki tych starszych procedur przechowywanych, 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 języku C #, aby wykonać pewne manipulacje ( chociaż nie ma prawie żadnych ). Każda procedura przechowywana zwraca inny zestaw danych ( kolumny tabeli ). Zamiast tworzyć dziesiątki klas lub struktur do przechowywania zwróconych 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;
}
dynamic np = Py.Import("numpy")
dynamic
gdy zastosujesz na nich operatory numeryczne. Zapewnia to bezpieczeństwo typu i pozwala uniknąć ograniczeń leków generycznych. To w istocie * pisanie kaczek:T y = x * (dynamic)x
, gdzie typeof(x) is T
Innym przykładem użycia do dynamic
pisania są metody wirtualne, w których występuje problem z kowariancją lub kontrawariancją. Jednym z takich przykładów jest niesławna Clone
metoda, która zwraca obiekt tego samego typu, co obiekt, do którego został wywołany. Ten problem nie został w pełni rozwiązany za pomocą dynamicznego powrotu, ponieważ omija sprawdzanie typu statycznego, ale przynajmniej nie trzeba przez cały czas używać brzydkich rzutów, jak w przypadku zwykłego object
. Innymi słowy, obsady 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
}
}