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


87

以下のコードは、クラスClassSameAssemblyと同じアセンブリにクラスがある限り、正常に機能しています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

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 \ Program.cs:line 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)
mehanik 2010

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)atSystem.Threading。 System.Threading.ThreadHelper.ThreadStart()でのExecutionContext.Run(ExecutionContext ExecutionContext、ContextCallbackコールバック、オブジェクト状態)InnerException:
mehanik 2010

完全なソースコードを使用した最終的な解決策はありますか?
kiquenet 2013

回答:


116

問題は、匿名型が次のように生成されることだと思います internalため、バインダーはそれ自体を実際には「認識」していません。

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

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

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

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

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

匿名型をリフレクションを介して同じ内容のexpandoに変換する拡張メソッドを作成できる可能性があります。次に、次のように書くことができます。

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

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


1
ジョンありがとう。たまたまアセンブリ専用のクラスを使用しても、同じ問題が発生しました。
デイブマークル2011年

2
最後にあなたの恐ろしい例のようなものが好きですが、それほど恐ろしいものではありません。使用するには:dynamic props = new {Metadata = DetailModelMetadata.Create、PageTitle = "New Content"、PageHeading = "Content Management"}; 動的メンバーとして名前付きの小道具を追加しておくと便利です。
ProfK 2011

オープンソースフレームワークの即興インターフェースは、動的または静的なオブジェクトに対して機能するインライン初期化構文を備えたdlrで多くのことを行います。 return Build<ExpandoObject>.NewObject(Name:"Micheal", Age: 20);
jbtule 2011年

1
匿名型をexpandoに変換する拡張メソッドの完全なソースコードサンプルはありますか?
キケネット2013

1
@ Md.lbrahim:基本的にはできません。objectジェネリック型またはジェネリック型のいずれかでそれを実行し(クラスである必要があります...)、実行時に型を確認する必要があります。
Jon Skeet 2014年

63

[assembly: InternalsVisibleTo("YourAssemblyName")]アセンブリの内部を表示するために使用できます。


2
ジョンの答えはもっと完全ですが、これは実際には私にとってかなり簡単な回避策を提供します。ありがとう:)
kelloti 2012年

私はさまざまなフォーラムで何時間も頭を叩いていましたが、これ以外の簡単な答えは見つかりませんでした。ルークに感謝します。しかし、それでも、同じアセンブリのように、アセンブリの外部で動的タイプにアクセスできない理由を理解できませんか?つまり、.Netでこの制限があるのはなぜですか。
ファイサルMq 2013

@FaisalMqこれは、匿名クラスを生成するコンパイラがそれらを「内部」と宣言しているためです。どちらが本当の理由かわからない。
ema 2013

2
はい、この答えは重要だと思います。作業コードを変更したくないので、別のアセンブリからテストする必要があります
PandaWood 2014年

ここで追加する1つの注意点は、これを機能させるには、この変更後にVisualStudioを再起動する必要があるということです。
レイディ2016年

11

私は同様の問題に遭遇し、別のオプションがあるというJonSkeetsの回答に追加したいと思います。私が見つけた理由は、Asp MVC3の多くの拡張メソッドが、html属性を提供するための入力として匿名クラスを使用していることに気付いたためです(new {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"]);

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

お役に立てれば!

/ビクター


その混乱をお詫びします。必要に応じて、prop1 => p1から更新されたコード。それでも-投稿全体のアイデアは、問題を解決するためのオプションとしてTypeDescriptor.GetPropertiesを提案することでしたが、とにかく明らかになったと思います...
Victor

これは、ダイナミックが私たちのためにこれを行うことができないことは本当に愚かです。私はダイナミックが大好きで、嫌いです。
Chris Marisic 2014

2

これは、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);
    }

1

よりクリーンな解決策は次のとおりです。

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

これがExpandoObjectになりました。

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

Microsoft.CSharp.dll

1

以下のソリューションは、私のコンソールアプリケーションプロジェクトで機能しました

この[アセンブリ:InternalsVisibleTo( "YourAssemblyName")]を、動的オブジェクトを返す関数を使用して、別のプロジェクトの\ Properties \ AssemblyInfo.csに配置します。

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


0

勇敢な人のための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

プロジェクトですでにNewtonsoft.Jsonを使用している場合(またはこの目的で追加する場合)、JonSkeetが次のように回答で参照している恐ろしい拡張メソッドを実装できます。

public static class ObjectExtensions
{
    public static ExpandoObject ToExpando(this object obj)
        => JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(obj));
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.