web-dev-qa-db-fra.com

Ajout de membres à un objet dynamique lors de l'exécution

J'explore le modèle DynamicObject dans .NET 4.0. L'application est celle où un objet sera décrit par une sorte de fichier texte/xml, et le programme doit créer un objet lors de la lecture de ce fichier.

Avec DynamicObject, nous pouvons facilement ajouter des membres, étant donné que nous connaissons le nom du membre a priori. Mais que se passe-t-il si nous ne savons même pas le nom du membre à ajouter? Existe-t-il un moyen de rendre cette dynamique également?

Par exemple, disons que je dois créer un objet avec les membres 'Property1', 'Property2' et un autre objet avec 'PropertyA' et 'PropertyB' comme décrit par le fichier texte/XML. Comment puis-je créer un objet dynamiquement sur la base de ces informations?

[~ # ~] mise à jour [~ # ~] J'ai eu quelques idées de ce post: http://www.codeproject.com /KB/cs/dynamicincsharp.aspx

Cette implémentation me permet de faire quelque chose comme ceci:

dynamic d = new PFDynamicChannel();
PFCouplings c = ((PFChannel)d).Coupling;
d.NewProperty = "X";

La raison pour laquelle je ne souhaite pas utiliser un dictionnaire est d'utiliser les méthodes TryGetMember et TrySetMember que je peux remplacer, dans lesquelles je peux déclencher des événements qui sont essentiels pour le programme.

De cette façon, je peux hériter d'une classe de base (PFChannel), mais je peux également ajouter des membres à la volée. Mais, mon problème est que je ne connais pas le nouveau nom de propriété avant l'exécution. Et, en fait, je ne pense pas que l'objet dynamique me permette d'ajouter de nouvelles propriétés à la volée. Si tel est le cas, comment puis-je utiliser ExpandoObject pour me donner cette capacité?

29
sbenderli

Si vous seulement devez le faire, vous devriez regarder ExpandoObject . Si vous devez le faire et utilisez toujours DynamicObject, vous devrez écrire du code pour mémoriser les valeurs des propriétés, essentiellement ... qui vous pourriez potentiellement le faire avec un ExpandoObject intégré.

Cependant, je ne sais pas ce que vous voulez faire avec cet objet par la suite - êtes-vous sûr d'avoir besoin d'une frappe dynamique? Un Dictionary<string, object> en fait être pire? Cela dépend de ce qui va consommer l'objet plus tard.

45
Jon Skeet

Selon ceci: Ajout de propriétés et de méthodes à un ExpandoObject, dynamiquement! ,

... vous pouvez utiliser un objet expando en tant que détenteur de valeur et le convertir en une <chaîne, objet> IDictionary lorsque vous souhaitez ajouter des propriétés nommées dynamiquement.

Exemple

dynamic myobject = new ExpandoObject();

IDictionary<string, object> myUnderlyingObject = myobject;

myUnderlyingObject.Add("IsDynamic", true); // Adding dynamically named property

Console.WriteLine(myobject.IsDynamic); // Accessing the property the usual way

Ceci est testé et sera imprimé "vrai" sur l'écran de la console.

Bien sûr, dans votre cas, où votre objet sous-jacent doit hériter d'une autre classe, cet exemple est donné juste pour vous donner une idée d'une implémentation personnalisée potentielle.

Peut-être inclure un objet expando dans votre implémentation de classe et rediriger les appels vers tryget et tryset vers l'instance de l'objet expando dans votre classe?

[~ # ~] mise à jour [~ # ~]

SI votre classe de base dérive de DynamicObject (ce qui signifie que vous pouvez remplacer toutes les méthodes TrySet/Get/Invoke), vous pouvez également utiliser un dictionnaire en interne. Dans les substitutions try get/set, vous effectuez tous les événements que vous souhaitez et déléguez le paramètre au dictionnaire interne.

Pour ajouter une nouvelle propriété (ou supprimer une propriété existante), vous pouvez remplacer TryInvoke. Lorsque le nom mothod est, par exemple, "AddProperty" et qu'il y a un argument de type chaîne, vous devez ajouter un nouvel élément dans votre dictionnaire avec le nom de l'argument. De même, vous définiriez dynamiquement un "RemoveProperty", etc. Vous n'avez même pas besoin d'un objet expando.

class MyBaseClass: DynamicObject
{
    // usefull functionality
}

class MyClass: MyBaseClass
{
    Dictionary<string, object> dynamicProperties = new Dictionary<string, object>();

    override bool TryGetMember(...)
    {
       // read the value of the requested property from the dictionary
       // fire any events and return
    }

    override bool TrySetMember(...)
    {
       // set the value of the requested property to the dictionary
       // if the property does not exist,
       // add it to the dictionary (compile time dynamic property naming)
       // fire any events
    }

    override bool TryInvoke(...)
    {
       // check what method is requested to be invoked
       // is it "AddProperty"??
       // if yes, check if the first argument is a string
       // if yes, add a new property to the dictionary
       // with the name given in the first argument (runtime dynamic property naming)
       // if there is also a second argument of type object,
       // set the new property's value to that object.

       // if the method to be invoked is "RemoveProperty"
       // and the first argument is a string,
       // remove from the Dictionary the property
       // with the name given in the first argument.

       // fire any events
    }
}

// USAGE
static class Program
{
    public static void Main()
    {
        dynamic myObject = new MyClass();

        myObject.FirstName = "John"; // compile time naming - TrySetMember
        Console.WriteLine(myObject.FirstName); // TryGetMember

        myObject.AddProperty("Salary");  // runtime naming (try invoke "AddProperty" with argument "Salary")
        myObject.Salary = 35000m;
        Console.WriteLine(myObject.Salary); // TryGetMember

        myObject.AddProperty("DateOfBirth", new DateTime(1980,23,11)); // runtime naming (try invoke "AddProperty" with fisrt argument "DateOfBirth" and second argument the desired value)
        Console.WriteLine(myObject.DateOfBirth); // TryGetMember

        myObject.RemoveProperty("FirstName"); // runtime naming (try invoke "RemoveProperty" with argument "FirstName")

        Console.WriteLine(myObject.FirstName); // Should print out empty string (or throw, depending on the desired bahavior) because the "FirstName" property has been removed from the internal dictionary.

    }
}

Bien sûr, comme je l'ai dit, cela ne fonctionnerait que si votre classe de base dérive de DynamicObject.

33
Thanasis Ioannidis

Je ne suis pas sûr que vous souhaitiez utiliser un objet dynamique dans ce cas.

dynamique en c # vous permet de faire des choses comme:

  dynamic something = GetUnknownObject();
  something.aPropertyThatShouldBeThere = true;

Si vous utilisez un ExpandoObject , vous pouvez:

  var exp = GetMyExpandoObject();
  exp.APropertyThatDidntExist = "something";

Ces deux options vous permettent d'utiliser le nom de propriété comme s'il existait réellement au moment de la compilation . Dans votre cas, vous n'aurez pas du tout besoin d'utiliser cette syntaxe, donc je ne suis pas sûr que cela vous apporterait un quelconque avantage. Au lieu de cela, pourquoi ne pas simplement utiliser un Dictionary<string, object>, et:

var props = new Dictionary<string, object>();
   foreach( var someDefinintion in aFile)
   {
      props[ someDefinition.Name] = someDefinition.value;
   }

Parce qu'un Dictionary<string,object> est essentiellement ce qu'est un objet expando - mais il prend en charge une syntaxe différente. Oui, je simplifie - mais si vous n'utilisez pas cela à partir d'un autre code ou via une liaison/etc, cela est fondamentalement vrai.

7
Philip Rieck

En relation avec la réponse de Thanasis Ioannidis concernant une variante "Si votre classe de base dérive de DynamicObject", il existe un moyen plus simple, il suffit d'ajouter la méthode AddProperty dans la classe "MyClass" comme ceci:

    class MyClass: MyBaseClass
    {
        Dictionary<string, object> dynamicProperties = new Dictionary<string, object>();

        public void AddProperty(string key, object value){
            dynamicProperties[key] = value;
        }

Ensuite, vous pouvez utiliser

dynamic myObject = new MyClass();
myObject.AddProperty("DateOfBirth", new DateTime(1980,23,11));

Vous n'avez pas besoin de passer outre à "TryInvoke".

1
Marko Rakić

Le type dynamic est essentiellement implémenté en tant que Property Bag. Cela signifie son dictionnaire de clés (noms de propriété) et d'objets (ici de manière non sécurisée par type). Je ne l'utilise pas si vous pouvez accéder directement à ce dictionnaire, mais vous pouvez utiliser le dictionnaire par vous-même.

Merde, Jon Skeet était plus rapide :(

0
Euphoric