web-dev-qa-db-ja.com

C#「動的」は、別のアセンブリで宣言された匿名型のプロパティにアクセスできません

クラスClassSameAssemblyと同じAssemblyにクラスProgramがある限り、以下のコードは正常に機能します。ただし、クラスClassSameAssemblyを別のアセンブリに移動すると、RuntimeBinderException(以下を参照)がスローされます。解決することは可能ですか?

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「オブジェクト」には「名前」の定義が含まれていません

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

問題は、匿名型がinternalとして生成されるため、バインダーがそれを実際に「知らない」ことだと思います。

代わりにExpandoObjectを使用してみてください。

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

私はそれがややknowいことを知っていますが、現時点では考えることができる最高です... ExpandoObjectとして強く型付けされている間、コンパイラは何をすべきかわからないので、オブジェクト初期化子を使用することさえできないと思います「名前」と「年齢」で行います。 mayこれを行うことができます:

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

しかし、それはあまり良くありません...

潜在的に拡張メソッドを記述して、リフレクションを介して匿名コンテンツを同じ内容のエキスパンドに変換できます。次に、あなたは書くことができます:

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

それはかなり恐ろしいことです:(

113
Jon Skeet

[Assembly: InternalsVisibleTo("YourAssemblyName")]を使用して、アセンブリ内部を表示できます。

61
ema

私は同様の問題にぶつかったので、ジョン・スキーツに別の選択肢があるという答えを付け加えたいと思います。私が見つけた理由は、Asp MVC3の多くの拡張メソッドが、入力として匿名クラスを使用してhtml属性を提供することに気づいたからです(新しい{alt = "Image alt"、style = "padding-top:5px"} =>

とにかく-これらの関数はRouteValueDictionaryクラスのコンストラクターを使用します。私は自分でそれを試してみましたが、それが十分に機能することを確認しました-ただし、最初のレベルのみ(マルチレベル構造を使用しました)。 SO-コードでは、これは次のようになります。

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 ...ここで実際に何が起こっているのですか? RouteValueDictionaryの内部を覗くと、このコードが明らかになります(上記の値〜= o):

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-TypeDescriptor.GetProperties(o)を使用すると、別のアセンブリの内部として匿名型が構築されているにもかかわらず、プロパティと値を取得できます。そしてもちろん、これを拡張して再帰的にするのは非常に簡単です。必要に応じて拡張メソッドを作成します。

お役に立てれば!

/ビクター

10
Victor

ToExpandoObjectの拡張メソッドの初歩的なバージョンを次に示しますが、これには磨きの余地があると確信しています。

    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

よりクリーンなソリューションは次のとおりです。

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

これは現在ExpandoObjectです。

参照することを忘れないでください:

Microsoft.CSharp.dll
1
Zylv3r

既にプロジェクトでNewtonsoft.Jsonを使用している場合(またはこの目的で追加する場合)、そのhorrible拡張機能を実装できますJon Skeetが his answer で参照しているメソッド:

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

以下のソリューションは、私のコンソールアプリケーションプロジェクトで私のために働いた

この[Assembly:InternalsVisibleTo( "YourAssemblyName")]を、動的オブジェクトを返す関数を含む別のプロジェクトの\ Properties\AssemblyInfo.csに配置します。

「YourAssemblyName」は、呼び出し元プロジェクトのアセンブリ名です。呼び出しプロジェクトで実行することにより、Assembly.GetExecutingAssembly()。FullNameを介して取得できます。

0
Shah

ToExpando拡張メソッド(Jonの回答で言及)

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