DynamicObjectについて知る

言語に新しい興味深い機能を追加するたびに、その機能を最大限に活用し始める人々が常に現れます。 DynamicObjectは単純で理解しやすいように見える機能ですが、遊び心のあるペンでは、より危険な仕事になります。

知人


まず、 System.Dynamic.DynamicObjectクラスが何であるかを見てみましょう。 このクラスは普通のようです-たとえば、このクラスから継承して、1つ以上のメソッドをオーバーロードできます。 ここに難しい方法がいくつかあります...詳しく見てみましょう。

まず、 DOテストオブジェクトを作成し、 DynamicObjectから継承しDynamicObject

class DO : DynamicObject {}

ここで、 dynamicキーワードを使用して、後悔することなくこのオブジェクトのメソッドを呼び出すことができます。

dynamic dobj = new DO();
dobj.NonExistentMethod();

私たちが得るものを推測します。 RuntimeBinderExceptionと呼ばれるRuntimeBinderExceptionとこのメッセージが表示されます。

'DynamicObjectAbuse.DO' does not contain a definition for 'NonExistentMethod'

当然です このクラスには、 NonExistentMethod()メソッドがありません。 しかし、興味深いのは、それが決してありえないということです。 それDynamicObjectクラスにないプロパティとメソッドを呼び出す機能です。 コンパイル時ではないか、まったくありません。

存在しないメソッドの佐賀


これはどのように起こりましたか? 非常にシンプル-メソッドを呼び出すとき、実際にメソッドを呼び出します

bool TryInvokeMember(InvokeMemberBinder binder, object [] args, out object result)

NonExistentMethod()メソッドの呼び出しの場合、このメソッドは引数なしで呼び出され、 binderパラメーターには呼び出しに関する情報が含まれます。

{Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder}
[Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder]: {Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder}
base {System.Dynamic.DynamicMetaObjectBinder}: {Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder}
CallInfo: {System.Dynamic.CallInfo}
IgnoreCase: false
Name: "NonExistentMethod"
ReturnType: {Name = "Object" FullName = "System.Object" }

この場合、何らかの方法で処理できるメソッドの名前を取得します。 方法-それはあなた次第です。 任意のビジネスロジックを使用できます。 繰り返しますが、引数を受け取り( args )、結果を返す( result )ためのメカニズムがあります。 このメソッドは、すべてがうまくいった場合はtrue返し、すべてがうまくいかなかった場合はfalse返しtrue 。 このメソッドからfalse 、上で見た例外が発生します。

メソッド以外に何がありますか?


DynamicObjectのオーバーロードされた操作のセットDynamicObject印象的です。 これは主に、存在しないプロパティへのアクセス、変換、単項演算子および二項演算子、インデックスを介したアクセスなどに応答する機能です。 一部の操作は、C#/ VBをまったく想定していません。たとえば、オブジェクトの作成のインターセプト、オブジェクトのメンバーの削除、インデックスによるオブジェクトの削除などです。

小さなインシデントが1つあります。 thisにより、静的に型指定された動的DO代わりに静的DOオブジェクトが取得されます。 この問題の解決策は予測可能です。

private dynamic This { get { return this ; } }

これは、本当に必要な場合に使用します。 もちろん、 TryInvokeMember()からThisメソッドを呼び出すなどの愚かなことはしないでください。 StackOverflowExceptionます。

Expandoobject


ExpandoObjectは通常、白鳥の歌です。 このクラスにより、ユーザーはメソッドとプロパティを任意に追加できます。

dynamic eo = new ExpandoObject();
eo.Name = "Dmitri" ;
eo.Age = 25;
eo.Print = new Action(() =>
Console.WriteLine( "{0} is {1} years old" ,
eo.Name, eo.Age));
eo.Print();

このようなオブジェクトをシリアル化することは、簡単な作業ではありません。 IDictionary実装します。たとえば、アセンブリとインターフェイスの断片化に関連する非常に曖昧な理由により、XMLでシリアル化されないインターフェイスです。 関係ありません 本当に必要な場合は、 System.Runtime.Serialization.DataContractSerializerを使用できます。

var s = new DataContractSerializer( typeof (IDictionary< string , object >));
var sb = new StringBuilder();
using ( var xw = XmlWriter.Create(sb))
{
s.WriteObject(xw, eo);
}
Console.WriteLine(sb.ToString());

当然、そのようなことはメソッドをシリアル化しません。 これを行うには、 DataContractResolver中心にタンバリンを使用してダンスを整理できますが、このようなプラクティスはこの記事の目的ではありません。

それをどうしますか?


OK、一般に、機能はCOM開発の観点から理解できます。COM開発では、多かれ少なかれ深刻な相互作用はそれぞれ、オージェのstable舎をクリアすることに似ています。 動的言語との相互作用も良いプラスです。少なくともこのことに興味があれば、この記事ではこれらすべてのバインダーやその他のインフラの魅力について明確に説明します。

ここに、可能な限り引用されている素晴らしい例があります(したがって、これは盗作ではないことを望みます)。 XElement 、XMLを使用する場合、 XElement要素と属性へのアクセスは単純に非人間的です。

var xe = XElement.Parse(something);
var name = xe.Elements( "People" ).Element( "Dmitri" ).Attributes( "Name" ).Value; // WTF?

これは単なる非人間的な構文です。 これは、はるかに「魅力的な」ソリューションです。最初に、 XElement DynamicObjectコンテンツをその仮想プロパティでDynamicObjectするDynamicObjectを作成します。

public class DynamicXMLNode : DynamicObject
{
XElement node;
public DynamicXMLNode(XElement node)
{
this .node = node;
}
public DynamicXMLNode()
{
}
public DynamicXMLNode(String name)
{
node = new XElement(name);
}
public override bool TrySetMember(
SetMemberBinder binder, object value )
{
XElement setNode = node.Element(binder.Name);
if (setNode != null )
setNode.SetValue( value );
else
{
if ( value .GetType() == typeof (DynamicXMLNode))
node.Add( new XElement(binder.Name));
else
node.Add( new XElement(binder.Name, value ));
}
return true ;
}
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
XElement getNode = node.Element(binder.Name);
if (getNode != null )
{
result = new DynamicXMLNode(getNode);
return true ;
}
else
{
result = null ;
return false ;
}
}
}

さて、それは小さなこと次第です-プロパティが必要な場合は、単にポイントを介してそれを取ることができます:

var xe = XElement.Parse(something);
var dxn = new DynamicXmlNode(xe);
var name = dxn.people.dmitri.name;

モナドとAOP


繰り返しになりますが、このようなオブジェクトへのアクセスを制御する機能を備えているため、Unity.Interceptorまたは別のIoC + AOPフレームワークのスタイルでAOPを接続できます。 たとえば、上記の例では、チェーンの要素の1つが実際にnullであってもNullReferenceException を決して NullReferenceException ないことを保証できnull 。 これを行うには、偽のオブジェクトを作成する必要がありますが、これは流なインターフェイスの中間クラスを作成することに似ています。

DSL'ki


もちろん、クラスで「何かを書く」能力は、原則として、静的にチェックできない(MPSスタイルの構文とは異なります)が、これに基づいてDSLを構築できるという考えに至りますが、難しいドメイン言語をいくつか説明してください。

「やめなさい」と言いますが、「文字列、ジェネレーター、その他のメタインフラストラクチャを使用する方が簡単ではありませんか?」実際、それはすべてあなたの見方に依存します。 たとえば、 DynamicXmlNodeは、 XMLがドメインであるDSLです。 同様に、たとえば次のように書くことができます。

myobject.InvokeEachMethodThatBeginsWithTest()

教訓は、 DynamicObject InvokeEachMethod...行を愚かにInvokeEachMethod...それに応じて応答するということです。 この場合、反射を使用します。 もちろん、これはDSLとしてこの機能を使用することは、a)完全に文書化されておらず、第三者には理解できないことを意味します。 およびb)識別子の命名規則によって制限されます。 たとえば、次をコンパイルすることはできません。

DateTime dt = (DateTime)timeDO.friday.13.fullmoon;

ただし、 friday13コンパイルは成功します。 ただし、既にJuly()などの拡張メソッドが既にあり(おそらく本番4.July(2010)で使用されていますJuly()4.July(2010)ような非常に不可解なコードを記述できます。 私にとっては、これはまったくクールではありません。

例へのリンク


以下は、 DynamicObjectな人々が彼らの地獄の目標のためにDynamicObjectメカニズムを使用する方法の例です。


要するに、この不名誉を無条件に「標準的な」プログラミングと呼ぶことはできませんが、多くのユースケースがあります。 静的チェックの欠如は、文盲で使用されたときに検出できないバグの束になる可能性があると確信していますので、あなたへの私のアドバイスは注意することです!

Source: https://habr.com/ru/post/J96988/


All Articles