88 votes

C# "dynamique" ne peut pas accéder aux propriétés des types anonymes déclarés dans une autre assemblée.

Le code ci-dessous fonctionne bien tant que je dispose de la classe ClassSameAssembly dans le même assemblage que la classe Program . Mais quand je change de classe ClassSameAssembly à une assemblée séparée, une RuntimeBinderException (voir ci-dessous) est lancé. Est-il possible de le résoudre ?

using System;

namespace ConsoleApplication2
{
    public static class ClassSameAssembly
    {
        public static dynamic GetValues()
        {
            return new
            {
                Name = "Michael", Age = 20
            };
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var d = ClassSameAssembly.GetValues();
            Console.WriteLine("{0} is {1} years old", d.Name, d.Age);
        }
    }
}

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : object" ne contient pas de définition pour "Name".

at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at ConsoleApplication2.Program.Main(String[] args) in C:\temp\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:line 23

0 votes

StackTrace : at CallSite.Target(Closure , CallSite , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0) at ConsoleApplication2.Program.Main(String[] args) in C:\temp\Projects\ConsoleApplication2\ConsoleApplication2\Pro gram.cs:ligne 23 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)

0 votes

At Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext. Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException :

0 votes

Une solution finale avec le code source complet ?

118voto

Jon Skeet Points 692016

Je pense que le problème est que le type anonyme est généré en tant que internal Le classeur n'est donc pas vraiment "au courant" en tant que tel.

Essayez d'utiliser ExpandoObject à la place :

public static dynamic GetValues()
{
    dynamic expando = new ExpandoObject();
    expando.Name = "Michael";
    expando.Age = 20;
    return expando;
}

Je sais que c'est un peu moche, mais c'est ce que j'ai trouvé de mieux pour le moment... Je ne pense pas que vous puissiez même utiliser un initialisateur d'objet avec lui, parce que même s'il est fortement typé en tant que ExpandoObject le compilateur ne saura pas quoi faire avec "Name" et "Age". Vous mai être en mesure de le faire :

 dynamic expando = new ExpandoObject()
 {
     { "Name", "Michael" },
     { "Age", 20 }
 };
 return expando;

mais ce n'est pas beaucoup mieux...

Vous pourriez potentiellement écrire une méthode d'extension pour convertir un type anonyme en un expando avec le même contenu via la réflexion. Vous pourriez alors écrire :

return new { Name = "Michael", Age = 20 }.ToExpando();

C'est assez horrible quand même :(

1 votes

Merci Jon. J'ai eu le même problème en utilisant une classe qui s'est avérée être privée à l'assemblée.

2 votes

J'aimerais bien avoir quelque chose comme ton horrible exemple à la fin, mais pas aussi horrible. Pour utiliser : dynamic props = new {Metadata = DetailModelMetadata.Create, PageTitle = "New Content", PageHeading = "Content Management"} ; et avoir les props nommés ajoutés comme membres dynamiques serait génial !

0 votes

Le cadre open source interface impromptue fait beaucoup avec le dlr il a une syntaxe d'initialisation en ligne qui fonctionne pour tout objet dynamique ou statique. return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);

63voto

ema Points 2346

Vous pourriez utiliser [assembly: InternalsVisibleTo("YourAssemblyName")] pour rendre visible les internes de votre assemblage.

2 votes

La réponse de Jon est plus complète, mais cette solution me permet de trouver une solution relativement simple. Merci :)

0 votes

Je me suis tapé la tête pendant des heures sur différents forums mais je n'ai trouvé aucune réponse simple, sauf celle-ci. Merci Luke. Mais je n'arrive toujours pas à comprendre pourquoi un type dynamique n'est pas accessible en dehors d'une assembly comme il l'est dans la même assembly ? Je veux dire pourquoi cette restriction dans .Net.

0 votes

@FaisalMq c'est parce que le compilateur qui génère les classes anonymes les déclare "internes". Je ne sais pas quelle est la vraie raison.

11voto

Victor Points 649

J'ai rencontré un problème similaire et j'aimerais ajouter à la réponse de Jon Skeets qu'il existe une autre option. La raison pour laquelle je l'ai découvert est que j'ai réalisé que de nombreuses méthodes d'extension dans Asp MVC3 utilisent des classes anonymes comme entrée pour fournir des attributs html (new {alt="Image alt", style="padding-top : 5px"} =>

Quoi qu'il en soit, ces fonctions utilisent le constructeur de la classe RouteValueDictionary. J'ai essayé moi-même, et bien sûr, cela fonctionne, mais seulement pour le premier niveau (j'ai utilisé une structure à plusieurs niveaux). Donc, en code, ce serait :

object o = new {
    name = "theName",
    props = new {
        p1 = "prop1",
        p2 = "prop2"
    }
}
SeparateAssembly.TextFunc(o)

//In SeparateAssembly:
public void TextFunc(Object o) {
  var rvd = new RouteValueDictionary(o);

//Does not work:
Console.WriteLine(o.name);
Console.WriteLine(o.props.p1);

//DOES work!
Console.WriteLine(rvd["name"]);

//Does not work
Console.WriteLine(rvd["props"].p1);
Console.WriteLine(rvd["props"]["p1"]);

Alors... Que se passe-t-il vraiment ici ? Un coup d'oeil à l'intérieur du RouteValueDictionary révèle ce code (valeurs ~= o ci-dessus) :

foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
    object obj2 = descriptor.GetValue(values);
    //"this.Add" would of course need to be adapted
    this.Add(descriptor.Name, obj2);
}

Ainsi, en utilisant TypeDescriptor.GetProperties(o), nous serions en mesure d'obtenir les propriétés et les valeurs malgré le fait que le type anonyme soit construit comme interne dans une assemblée séparée ! Et bien sûr, il serait assez facile de l'étendre pour le rendre récursif. Et pour créer une méthode d'extension si vous le souhaitez.

J'espère que cela vous aidera !

/Victor

0 votes

Désolé pour cette confusion. Code mis à jour de prop1 => p1 où approprié. Quand même - l'idée avec le post entier était de mettre en avant TypeDescriptor.GetProperties comme une option pour résoudre le problème, qui, je l'espère, était clair de toute façon...

0 votes

C'est vraiment stupide que Dynamic ne puisse pas faire ça pour nous. J'aime et je déteste vraiment Dynamic.

2voto

Ryan Rodemoyer Points 1798

Voici une version rudimentaire d'une méthode d'extension pour ToExpandoObject qui, j'en suis sûr, peut être améliorée.

    public static ExpandoObject ToExpandoObject(this object value)
    {
        // Throw is a helper in my project, replace with your own check(s)
        Throw<ArgumentNullException>.If(value, Predicates.IsNull, "value");

        var obj = new ExpandoObject() as IDictionary<string, object>;

        foreach (var property in value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            obj.Add(property.Name, property.GetValue(value, null));
        }

        return obj as ExpandoObject;
    }

    [TestCase(1, "str", 10.75, 9.000989, true)]
    public void ToExpandoObjectTests(int int1, string str1, decimal dec1, double dbl1, bool bl1)
    {
        DateTime now = DateTime.Now;

        dynamic value = new {Int = int1, String = str1, Decimal = dec1, Double = dbl1, Bool = bl1, Now = now}.ToExpandoObject();

        Assert.AreEqual(int1, value.Int);
        Assert.AreEqual(str1, value.String);
        Assert.AreEqual(dec1, value.Decimal);
        Assert.AreEqual(dbl1, value.Double);
        Assert.AreEqual(bl1, value.Bool);
        Assert.AreEqual(now, value.Now);
    }

1voto

Zylv3r Points 21

Une solution plus propre serait :

var d = ClassSameAssembly.GetValues().ToDynamic();

Qui est maintenant un ExpandoObject.

N'oubliez pas de faire référence :

Microsoft.CSharp.dll

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X