C # 4.0 a introduit un nouveau type appelé «dynamique». Tout cela sonne bien, mais à quoi servirait un programmeur?
Y a-t-il une situation où cela peut sauver la situation?
C # 4.0 a introduit un nouveau type appelé «dynamique». Tout cela sonne bien, mais à quoi servirait un programmeur?
Y a-t-il une situation où cela peut sauver la situation?
Réponses:
Le mot-clé dynamique est nouveau dans C # 4.0 et est utilisé pour indiquer au compilateur que le type d'une variable peut changer ou qu'il n'est pas connu avant l'exécution. Considérez-le comme étant capable d'interagir avec un objet sans avoir à le lancer.
dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!
Notez que nous n'avons pas eu besoin de lancer ou de déclarer cust en tant que client de type. Parce que nous l'avons déclaré dynamique, le runtime prend le relais, puis recherche et définit la propriété FirstName pour nous. Maintenant, bien sûr, lorsque vous utilisez une variable dynamique, vous abandonnez la vérification du type de compilateur. Cela signifie que l'appel cust.MissingMethod () sera compilé et n'échouera pas jusqu'à l'exécution. Le résultat de cette opération est une RuntimeBinderException car MissingMethod n'est pas défini sur la classe Customer.
L'exemple ci-dessus montre comment la dynamique fonctionne lors de l'appel de méthodes et de propriétés. Une autre fonctionnalité puissante (et potentiellement dangereuse) est la possibilité de réutiliser des variables pour différents types de données. Je suis sûr que les programmeurs Python, Ruby et Perl peuvent penser à un million de façons de tirer parti de cela, mais j'utilise C # depuis si longtemps que cela me semble "mal".
dynamic foo = 123;
foo = "bar";
OK, donc vous n'écrirez probablement pas très souvent du code comme celui-ci. Il peut cependant arriver que la réutilisation de variables puisse être utile ou nettoyer un morceau de code hérité sale. Un cas simple que je rencontre souvent est de devoir constamment convertir entre décimal et double.
decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");
La deuxième ligne ne compile pas car 2.5 est tapé comme un double et la ligne 3 ne compile pas car Math.Sqrt attend un double. Évidemment, tout ce que vous avez à faire est de convertir et / ou de changer votre type de variable, mais il peut y avoir des situations où la dynamique a du sens à utiliser.
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");
En savoir plus: http://www.codeproject.com/KB/cs/CSharp4Features.aspx
dynamic
in en c # pour résoudre des problèmes qui peuvent être résolus (peut-être même mieux) par des fonctionnalités standard de c # et du typage statique, ou tout au plus avec l'inférence de type ( var
). dynamic
doit seulement être utilisé en ce qui concerne les problèmes avec le DLR Interopérabilité. Si vous écrivez du code dans un langage typé statique, comme c # est, faites-le et n'émulez pas un langage dynamique. C'est juste moche.
dynamic
intensif des variables dans votre code où vous n'en avez pas besoin (comme dans votre exemple avec le squareroot), vous abandonnez la vérification des erreurs de temps de compilation; au lieu de cela, vous obtenez maintenant des erreurs d'exécution possibles.
le dynamic
mot-clé a été ajouté, ainsi que de nombreuses autres nouvelles fonctionnalités de C # 4.0, pour simplifier la conversation avec du code qui vit ou provient d'autres runtimes et qui possède différentes API.
Prenons un exemple.
Si vous avez un objet COM, comme le Word.Application
objet, et que vous souhaitez ouvrir un document, la méthode pour le faire est fournie avec pas moins de 15 paramètres, dont la plupart sont facultatifs.
Pour appeler cette méthode, vous auriez besoin de quelque chose comme ça (je simplifie, ce n'est pas du code réel):
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);
Notez tous ces arguments? Vous devez passer ceux-ci car C # avant la version 4.0 n'avait pas de notion d'arguments optionnels. Dans C # 4.0, les API COM ont été rendues plus faciles à utiliser en introduisant:
ref
facultatif pour les API COMLa nouvelle syntaxe de l'appel ci-dessus serait:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
Voyez à quel point il semble plus facile, à quel point il devient plus lisible?
Brisons cela:
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
La magie est que le compilateur C # va maintenant injecter le code nécessaire, et travailler avec de nouvelles classes dans le runtime, pour faire presque exactement la même chose que vous faisiez auparavant, mais la syntaxe vous a été cachée, maintenant vous pouvez vous concentrer sur le quoi , et pas tant sur le comment . Anders Hejlsberg aime dire que vous devez invoquer différentes "incantations", ce qui est une sorte de jeu de mots sur la magie du tout, où vous devez généralement agiter vos mains et dire des mots magiques dans le bon ordre. pour lancer un certain type de sort. L'ancienne façon API de parler aux objets COM était beaucoup de cela, vous aviez besoin de sauter à travers beaucoup de cerceaux afin d'amadouer le compilateur pour compiler le code pour vous.
Les choses se dégradent encore plus en C # avant la version 4.0 si vous essayez de parler à un objet COM pour lequel vous n'avez pas d'interface ou de classe, tout ce que vous avez est une IDispatch
référence.
Si vous ne savez pas ce que c'est, IDispatch
c'est essentiellement une réflexion pour les objets COM. Avec une IDispatch
interface, vous pouvez demander à l'objet "quel est le numéro d'identification de la méthode connue sous le nom de Save", et créer des tableaux d'un certain type contenant les valeurs des arguments, et enfin appeler une Invoke
méthode sur l' IDispatch
interface pour appeler la méthode, en passant tous les informations que vous avez réussi à graver ensemble.
La méthode Save ci-dessus pourrait ressembler à ceci (ce n'est certainement pas le bon code):
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, ...);
Tout cela pour simplement ouvrir un document.
VB avait des arguments et un support optionnels pour la plupart de ces éléments il y a longtemps, donc ce code C #:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
est fondamentalement juste C # rattraper VB en termes d'expressivité, mais le faire de la bonne façon, en le rendant extensible, et pas seulement pour COM. Bien sûr, cela est également disponible pour VB.NET ou tout autre langage intégré au runtime .NET.
Vous pouvez trouver plus d'informations sur l' IDispatch
interface sur Wikipedia: IDispatch si vous voulez en savoir plus. C'est vraiment des trucs sanglants.
Cependant, que se passe-t-il si vous souhaitez parler à un objet Python? Il existe une API différente de celle utilisée pour les objets COM, et comme les objets Python sont également de nature dynamique, vous devez recourir à la magie de la réflexion pour trouver les bonnes méthodes à appeler, leurs paramètres, etc., mais pas le .NET réflexion, quelque chose écrit pour Python, à peu près comme le code IDispatch ci-dessus, tout à fait différent.
Et pour Ruby? Une API différente encore.
JavaScript? Même accord, API différente pour cela également.
Le mot-clé dynamique se compose de deux choses:
dynamic
dynamic
mot clé a besoin et mappe les appels à la bonne façon de faire les choses. L'API est même documentée, donc si vous avez des objets provenant d'un runtime non couvert, vous pouvez l'ajouter.Le dynamic
mot clé n'est cependant pas destiné à remplacer tout code existant uniquement en .NET. Bien sûr, vous pouvez le faire, mais il n'a pas été ajouté pour cette raison, et les auteurs du langage de programmation C # avec Anders Hejlsberg à l'avant, ont été catégoriques: ils considèrent toujours le C # comme un langage fortement typé et ne sacrifieront pas ce principe.
Cela signifie que bien que vous puissiez écrire du code comme ceci:
dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;
et de le faire compiler, il ne s'agissait pas d'une sorte de système de type magic-lets-figure-out-what-you-mean-at-runtime.
L'objectif était de faciliter la conversation avec d'autres types d'objets.
Il y a beaucoup de matériel sur Internet sur le mot-clé, les partisans, les opposants, les discussions, les diatribes, les éloges, etc.
Je vous suggère de commencer avec les liens suivants, puis de google pour en savoir plus:
dynamic
été ajouté, pour soutenir d'autres écosystèmes sur la façon dont l'invocation de méthodes de réflexion peut être effectuée, ainsi que pour fournir une sorte d'approche de boîte noire aux structures de données avec un moyen documenté d'y parvenir.
Je suis surpris que personne n'ait mentionné l' envoi multiple . La façon habituelle de contourner cela est via le modèle de visiteur et ce n'est pas toujours possible, vous vous retrouvez donc avec des is
chèques empilés .
Voici donc un exemple concret de ma propre application. Au lieu de faire:
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());
}
Tu fais:
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);
}
Notez que dans le premier cas ElevationPoint
est une sous-classe de MapPoint
et s'il n'est pas placé avant, MapPoint
il ne sera jamais atteint. Ce n'est pas le cas avec dynamique, car la méthode de correspondance la plus proche sera appelée.
Comme vous pouvez le deviner à partir du code, cette fonctionnalité était utile pendant que j'effectuais la traduction des objets ChartItem vers leurs versions sérialisables. Je ne voulais pas polluer mon code avec les visiteurs et je ne voulais pas non plus polluer mes ChartItem
objets avec des attributs spécifiques de sérialisation inutiles.
is
empilé les uns sur les autres.
magic
; la magie n'existe pas.
Il facilite l'interopérabilité des langages typés statiques (CLR) avec les langages dynamiques (python, ruby ...) fonctionnant sur le DLR (runtime de langage dynamique), voir MSDN :
Par exemple, vous pouvez utiliser le code suivant pour incrémenter un compteur en XML en C #.
Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);
En utilisant le DLR, vous pouvez utiliser le code suivant à la place pour la même opération.
scriptobj.Count += 1;
MSDN répertorie ces avantages:
- Simplifie le portage de langages dynamiques vers .NET Framework
- Active les fonctionnalités dynamiques dans les langages typés statiquement
- Fournit les avantages futurs du DLR et du .NET Framework
- Permet le partage de bibliothèques et d'objets
- Fournit une répartition et une invocation dynamiques rapides
Voir MSDN pour plus de détails.
Un exemple d'utilisation:
Vous consommez de nombreuses classes qui ont une propriété commune '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; }
}
Si vous écrivez une méthode commun qui récupère la valeur de la propriété 'CreationDate', vous devrez utiliser la réflexion:
static DateTime RetrieveValueOfCreationDate(Object item)
{
return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
}
Avec le concept «dynamique», votre code est beaucoup plus élégant:
static DateTime RetrieveValueOfCreationDate(dynamic item)
{
return item.CreationDate;
}
Il sera principalement utilisé par les victimes RAD et Python pour détruire la qualité du code, IntelliSense et compiler la détection des bogues de temps.
Il évalue au moment de l'exécution, vous pouvez donc changer le type comme vous le pouvez en JavaScript vers ce que vous voulez. Cela est légitime:
dynamic i = 12;
i = "text";
Et vous pouvez donc changer le type selon vos besoins. Utilisez-le en dernier recours; c'est bénéfique, mais j'ai entendu beaucoup de choses se passer dans les coulisses en termes d'IL généré et cela peut avoir un prix de performance.
Le meilleur cas d'utilisation de variables de type «dynamique» pour moi était lorsque, récemment, j'écrivais une couche d'accès aux données dans ADO.NET (à l' aide de SQLDataReader ) et que le code invoquait les procédures stockées héritées déjà écrites. Il existe des centaines de ces procédures stockées héritées contenant la majeure partie de la logique métier. Ma couche d'accès aux données devait retourner une sorte de données structurées à la couche logique métier, basée sur C #, pour effectuer certaines manipulations ( bien qu'il n'y en ait presque pas ). Chaque procédure stockée renvoie un ensemble de données différent ( colonnes de table ). Donc, au lieu de créer des dizaines de classes ou de structures pour contenir les données renvoyées et les transmettre au BLL, j'ai écrit le code ci-dessous qui semble assez élégant et soigné.
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
appliquant des opérateurs numériques. Cela offre une sécurité de type et évite les limitations des génériques. Il s'agit essentiellement de la saisie de canard:T y = x * (dynamic)x
, où typeof(x) is T
Un autre cas d'utilisation pour la dynamic
saisie concerne les méthodes virtuelles qui rencontrent un problème de covariance ou de contravariance. Un tel exemple est la fameuse Clone
méthode qui renvoie un objet du même type que l'objet auquel il est appelé. Ce problème n'est pas complètement résolu avec un retour dynamique car il contourne la vérification de type statique, mais au moins vous n'avez pas besoin d'utiliser des conversions laides tout le temps comme lors de l'utilisation de plain object
. Autrement dit, les moulages deviennent implicites.
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
}
}