web-dev-qa-db-fra.com

C # ‘dynamic’ ne peut pas accéder aux propriétés de types anonymes déclarés dans un autre assemblage

Le code ci-dessous fonctionne bien tant que j'ai la classe ClassSameAssembly dans la même assemblée que la classe Program. Mais lorsque je déplace la classe ClassSameAssembly dans une assemblée séparée, un RuntimeBinderException (voir ci-dessous) est lancé . Est-il possible de résoudre il? 

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: 'objet' ne contient pas de définition pour 'Nom'

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
84
mehanik

Je pense que le problème est que le type anonyme est généré avec la valeur internal, de sorte que le classeur ne "sait" pas vraiment à ce sujet.

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 le meilleur que je puisse penser pour le moment ... Je ne pense pas que vous puissiez même utiliser un initialiseur d'objet avec celui-ci, car s'il est fortement typé comme ExpandoObject le compilateur ne saura pas quoi faire. faire avec "Nom" et "Age". Vous pouvez pouvez faire ceci:

 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 expando avec le même contenu via réflexion. Ensuite, vous pourriez écrire:

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

C'est assez horrible cependant :(

112
Jon Skeet

Vous pouvez utiliser [Assembly: InternalsVisibleTo("YourAssemblyName")] pour vous rendre visible en interne.

61
ema

J'ai rencontré un problème similaire et j'aimerais ajouter à la réponse de Jon Skeets qu'il existe une autre option. J'ai découvert 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é cela moi-même et, bien sûr, cela fonctionne - même si ce n'est que le premier niveau (j'ai utilisé une structure à plusieurs niveaux). SO - dans le code, cela 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"]);

SO ... Qu'est-ce qui se passe vraiment ici? Un coup d'œil à l'intérieur de 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);
}

SO - en utilisant TypeDescriptor.GetProperties (o), nous pourrions obtenir les propriétés et les valeurs même si le type anonyme a été construit de manière interne dans un assembly séparé! Et bien sûr, cela serait assez facile à étendre pour le rendre récursif. Et pour faire une méthode d'extension si vous vouliez.

J'espère que cela t'aides!

/Victor

10
Victor

Voici une version rudimentaire d'une méthode d'extension pour ToExpandoObject qui, j'en suis sûr, a de la place pour le polissage.

    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);
    }
2
Ryan Rodemoyer

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
1
Zylv3r

Si vous utilisez déjà Newtonsoft.Json dans votre projet (ou si vous souhaitez l'ajouter à cette fin), vous pouvez implémenter cette méthode d'extension horrible à laquelle Jon Skeet fait référence dans sa réponse like ce:

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
0
huysentruitw

La solution ci-dessous a fonctionné pour moi dans mes projets d'application de console

Mettez ceci [Assembly: InternalsVisibleTo ("YourAssemblyName")] Dans\Properties\AssemblyInfo.cs du projet séparé avec la fonction renvoyant un objet dynamique. 

"YourAssemblyName" est le nom d'assembly du projet appelant. Vous pouvez l'obtenir via Assembly.GetExecutingAssembly (). FullName en l'exécutant dans le projet appelant.

0
Shah

Méthode d'extension ToExpando (mentionnée dans la réponse de Jon) pour les plus courageux

public static class ExtensionMethods
{
    public static ExpandoObject ToExpando(this object obj)
    {
        IDictionary<string, object> expando = new ExpandoObject();
        foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(obj))
        {
            var value = propertyDescriptor.GetValue(obj);
            expando.Add(propertyDescriptor.Name, value == null || new[]
            {
                typeof (Enum),
                typeof (String),
                typeof (Char),
                typeof (Guid),
                typeof (Boolean),
                typeof (Byte),
                typeof (Int16),
                typeof (Int32),
                typeof (Int64),
                typeof (Single),
                typeof (Double),
                typeof (Decimal),
                typeof (SByte),
                typeof (UInt16),
                typeof (UInt32),
                typeof (UInt64),
                typeof (DateTime),
                typeof (DateTimeOffset),
                typeof (TimeSpan),
            }.Any(oo => oo.IsInstanceOfType(value))
                ? value
                : value.ToExpando());
        }

        return (ExpandoObject)expando;
    }
}
0