C#4.0の「動的」タイプは何に使用されますか?


236

C#4.0は「ダイナミック」と呼ばれる新しいタイプを導入しました。それはすべて良さそうに聞こえますが、プログラマーは何のためにそれを使用するでしょうか?

それがその日を救うことができる状況はありますか?



COMまたは動的に型付けされた言語を操作する場合に役立ちます。たとえば、言語のスクリプトを作成するためにluaまたはpythonを使用する場合は、通常のコードのようにスクリプトコードを呼び出すだけで非常に便利です。
CodesInChaos


私はこの記事があなたの質問に完全な答えがある願っていますvisualstudiomagazine.com/Articles/2011/02/01/...
開発者

回答:


196

dynamicキーワードはC#4.0の新機能であり、変数の型が変更される可能性があること、または実行時まで不明であることをコンパイラーに伝えるために使用されます。キャストしなくてもオブジェクトと対話できると考えてください。

dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!

custをタイプCustomerにキャストしたり宣言したりする必要がないことに注意してください。これを動的に宣言したため、ランタイムが引き継ぎ、FirstNameプロパティを検索して設定します。もちろん、動的変数を使用している場合は、コンパイラの型チェックをあきらめることになります。つまり、呼び出しcust.MissingMethod()はコンパイルされ、実行時まで失敗しません。MissingMethodがCustomerクラスで定義されていないため、この操作の結果はRuntimeBinderExceptionです。

上記の例は、メソッドとプロパティを呼び出すときにダイナミックがどのように機能するかを示しています。もう1つの強力な(そして潜在的に危険な)機能は、さまざまなタイプのデータの変数を再利用できることです。Python、Ruby、Perlのプログラマーは、これを利用する方法を100万通り考えられると確信していますが、私はC#を長い間使用しているので、「間違っている」と感じます。

dynamic foo = 123;
foo = "bar";

OK、それであなたはおそらく上記のようなコードを頻繁に書くことはないでしょう。ただし、変数の再利用が便利になったり、レガシーコードの汚れた部分をクリーンアップしたりできる場合があります。私がよく遭遇する単純なケースの1つは、10進数と2進数の間で常にキャストしなければならないことです。

decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");

Math.Sqrtがdoubleを想定しているため、2.5がdoubleとして入力されているため2行目はコンパイルされず、3行目はコンパイルされません。明らかに、あなたがしなければならないすべては、変数の型をキャストおよび/または変更することですが、動的を使用することが理にかなっている状況があるかもしれません。

dynamic foo = GetDecimalValue(); // still returns a decimal
foo = foo / 2.5; // The runtime takes care of this for us
foo = Math.Sqrt(foo); // Again, the DLR works its magic
string bar = foo.ToString("c");

機能をもっと読む:http : //www.codeproject.com/KB/cs/CSharp4Features.aspx


97
個人的にはdynamic、標準のc#機能と静的型付け、または型推論(var)で解決できる(多分さらに良い)問題を解決するためにc#を使用するという考えは好きではありません。DLRとの相互運用性の問題に関してのみ使用してdynamicください。C#のような静的型付き言語でコードを記述する場合は、それを行い、動的言語をエミュレートしないでください。それは醜いです。
フィリップドーブマイヤー2010

40
dynamic必要のないコードで変数を頻繁に使用する場合(squarerootの例のように)、クリーンなコンパイル時のエラーチェックをあきらめます。代わりに、実行時エラーが発生する可能性があります。
フィリップドーブマイヤー

33
ほとんど問題ありませんが、いくつかのマイナーなエラーがあります。第1に、動的とは変数の型が変わる可能性があることを意味するとは言えません。問題の変数のタイプは「動的」です(C#言語の観点から、CLRの観点から、変数はオブジェクトタイプです)。変数の型は決して変化しません。変数のの実行時型は、変数の型と互換性のある任意の型にすることができます。(または参照型の場合、nullにすることもできます。)
Eric Lippert 2010

15
2番目の点について:C#には、「何でも入れることができる変数を作成する」という機能がすでにありました。オブジェクト型の変数をいつでも作成できます。動的について興味深いことは、最初の段落で指摘したことです。動的はオブジェクトとほぼ同じですが、意味解析は実行時まで据え置かれ、意味解析は式の実行時の型に対して行われます。(主にいくつかの例外があります。)
エリックリッペルト2010

18
私はこれに反対票を投じました。これは主に、一般的な使用のためのキーワードの使用を暗示的に主張しているためです。これは具体的にターゲットを絞った目的(Lassesの回答で完全に説明されています)であり、この回答は(技術的には)正確ですが、開発者を迷わせる可能性があります。
Eight-Bit Guru

211

このdynamicキーワードは、C#4.0の他の多くの新機能と共に追加され、異なるAPIを持つ他のランタイムに存在する、または他のランタイムに由来するコードと簡単に対話できるようになりました。

例を挙げましょう。

オブジェクトのようなCOMオブジェクトがあり、Word.Applicationドキュメントを開きたい場合、それを行うためのメソッドには15以上のパラメーターがあり、そのほとんどはオプションです。

このメソッドを呼び出すには、次のようなものが必要になります(単純化していますが、これは実際のコードではありません)。

object missing = System.Reflection.Missing.Value;
object fileName = "C:\\test.docx";
object readOnly = true;
wordApplication.Documents.Open(ref fileName, ref missing, ref readOnly,
    ref missing, ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing);

それらすべての議論に注意してください?バージョン4.0より前のC#にはオプションの引数の概念がなかったため、これらを渡す必要があります。C#4.0では、COM APIは、以下を導入することで、より使いやすくなっています。

  1. オプションの引数
  2. 作るrefCOM APIのオプション
  3. 名前付き引数

上記の呼び出しの新しい構文は次のようになります。

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);

見た目がいかに簡単になり、読みやすくなりますか?

それを分解してみましょう:

                                    named argument, can skip the rest
                                                   |
                                                   v
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
                                 ^                         ^
                                 |                         |
                               notice no ref keyword, can pass
                               actual parameter values instead

魔法は、C#コンパイラが必要なコードを挿入し、ランタイムで新しいクラスを操作して、以前とほぼ同じことを行うことですが、構文は非表示になっているため、で、かつあまりありません。Anders Hejlsbergは、さまざまな "呪文"を呼び出す必要があると言っています。これは、全体の魔法の一種の駄洒落です。通常、手を振って、正しい順序でいくつかの魔法の言葉を言う必要があります。特定の種類の呪文を実行するために。COMオブジェクトと通信する古いAPIの方法はその多くでした。コンパイラーにコードをコンパイルするように協調させるには、多くのフープを飛び越える必要がありました。

インターフェースまたはクラスを持たないCOMオブジェクトと通信しようとすると、バージョン4.0より前のC#では状況がさらに悪化しIDispatchます。

それが何かわからない場合は、IDispatch基本的にはCOMオブジェクトのリフレクションです。ではIDispatchインターフェースあなたは「保存として知られている方法のためのID番号が何であるかを」オブジェクトを尋ねると、引数の値を含む特定の型の配列を構築し、最終的に呼び出すことができますInvoke上のメソッドをIDispatchメソッドを呼び出すためのインタフェース、すべて合格しますあなたが一緒に探し回った情報。

上記のSaveメソッドは次のようになります(これは間違いなく正しいコードではありません)。

string[] methodNames = new[] { "Open" };
Guid IID = ...
int methodId = wordApplication.GetIDsOfNames(IID, methodNames, methodNames.Length, lcid, dispid);
SafeArray args = new SafeArray(new[] { fileName, missing, missing, .... });
wordApplication.Invoke(methodId, ... args, ...);

これだけで、ドキュメントを開くだけです。

VBにはオプションの引数があり、ほとんどの場合、これらのほとんどがすぐにサポートされていたため、このC#コードは次のとおりです。

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);

基本的に、C#は表現力の点でVBに追いついていますが、COMだけでなく、拡張可能にすることにより、正しい方法でVBに追いついています。もちろん、これはVB.NETまたは.NETランタイムの上に構築されたその他の言語でも使用できます。

IDispatchインターフェースについて詳しくは、ウィキペディアのIDispatchを参照してください。それは本当に悲惨なものです。

ただし、Pythonオブジェクトと通信したい場合はどうでしょうか。COMオブジェクトに使用されているものとは異なるAPIがあり、Pythonオブジェクトも本質的に動的であるため、リフレクションマジックを使用して、呼び出す適切なメソッドやそのパラメーターなどを見つける必要がありますが、.NETではありません。リフレクション、Python用に書かれたもので、上記のIDispatchコードとほとんど同じですが、まったく異なります。

そしてRubyについては?まだ別のAPI。

JavaScript?同じことで、APIも異なります。

dynamicキーワードは、次の2つで構成されています。

  1. C#の新しいキーワード dynamic
  2. さまざまなタイプのオブジェクトの処理方法を認識し、dynamicキーワードに必要な特定のAPIを実装し、呼び出しを適切な方法にマップするランタイムクラスのセット。APIも文書化されているため、カバーされていないランタイムからのオブジェクトがある場合は、それを追加できます。

dynamicただし、このキーワードは、既存の.NETのみのコードを置き換えるものではありません。もちろん可能ですが、その理由で追加されませんでした。AndersHejlsbergを前に置いたC#プログラミング言語の作者は、C#を依然として強く型付けされた言語と見なしており、犠牲にすることはありません。その原則。

つまり、次のようなコードを記述できます。

dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;

それをコンパイルしても、それは一種のマジック・レッツ・フィギュア・アウト・アウト・ユー・ミーント・アット・ランタイム・システムのようなものではありませんでした。

全体の目的は、他のタイプのオブジェクトと簡単に対話できるようにすることでした。

キーワード、支持者、反対者、議論、怒り、賛美などについては、インターネット上にたくさんの資料があります。

以下のリンクから始めて、さらにグーグルで検索することをお勧めします。


12
シリアル化解除されたJSONオブジェクトの構造がC#で指定されていないWeb JSON APIのCOM以外にも役立ちます。たとえば、System.Web.Helpers.JsonのDecodeメソッドは動的オブジェクトを返します
dumbledad 2014年

「彼らはまだC#を強く型付けされた言語と見なしている」ということについての補足:Eric Lippertは説明として「強く型付けされた」のファンではありません
Andrew Keeton

私は彼に同意しませんが、それは意見の問題であり、事実の問題ではありません。私が「強く型付けされている」とは、コンパイラーがコンパイル時にどの型が使用されているかをコンパイラーが認識しているため、これらの型に設定された規則を適用することを意味します。ルールのチェックとランタイムへのバインドを延期する動的な型を選択できるという事実は、言語の型付けが弱いことを意味しません。私は通常、強く型付けされたものと弱い型付けされたものを対比しませんが、Pythonのような言語のように、動的に型付けされたものと比較します。
Lasse V. Karlsen

この答えの意味は何ですか?その半分は、オプションのパラメーターとIDispatchインターフェイスに関するものです。
Xam

そのdynamicため、リフレクションのようなメソッド呼び出しを実行するための他のエコシステムをサポートし、ドキュメント化された方法でデータ構造に一種のブラックボックスアプローチを提供するために追加されました。
Lasse V. Karlsen、

29

複数の派遣について誰も言及しなかったことに驚きます。これを回避する通常の方法は、訪問者パターンによるものであり、常に可能であるとは限らないため、スタックされたisチェックが発生します。

ここに私自身のアプリケーションの実際の例があります。代わりに:

public static MapDtoBase CreateDto(ChartItem item)
{
    if (item is ElevationPoint) return CreateDtoImpl((ElevationPoint)item);
    if (item is MapPoint) return CreateDtoImpl((MapPoint)item);
    if (item is MapPolyline) return CreateDtoImpl((MapPolyline)item);
    //other subtypes follow
    throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}

あなたがやる:

public static MapDtoBase CreateDto(ChartItem item)
{
    return CreateDtoImpl(item as dynamic);
}

private static MapDtoBase CreateDtoImpl(ChartItem item)
{
    throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}

private static MapDtoBase CreateDtoImpl(MapPoint item)
{
    return new MapPointDto(item);
}

private static MapDtoBase CreateDtoImpl(ElevationPoint item)
{
    return new ElevationDto(item);
}

最初のケースElevationPointはのサブクラスでMapPointあり、 MapPointに配置されていない場合は到達しないことに注意してください。これは、最も近い一致メソッドが呼び出されるため、動的の場合には当てはまりません。

コードから推測できるように、ChartItemオブジェクトからシリアル化可能なバージョンへの変換を実行しているときに、この機能が役立ちました。私はビジターでコードを汚染したくありませんでした。また、不要ChartItemなシリアル化固有の属性でオブジェクトを汚染したくありませんでした。


この使用例について知りませんでした。でも、せいぜい少しハッキーです。スタティックアナライザーがスローされます。
Kugel

2
@Kugelは本当ですが、私はそれをハックとは呼びません。静的分析は良いですが、選択肢が次のようなエレガントなソリューションから妨げられないようにします:開閉原理違反(ビジターパターン)、または恐ろしいisスタックを積み重ねたサイクロマティック複雑度の増加。
Stelios Adamantidis

4
C#7とのパターンマッチングのオプションはありますか?
Kugel

2
ええと、演算子は(ダブルキャストを回避することで)その方がはるかに安価であり、静的分析が返されます;-)とパフォーマンス。
Kugel

@idbrii私の答えを変更しないでください。私はこのコミュニティでまだ活動しているので、コメントを自由にドロップしてください。必要に応じて説明します。また、使用しないでくださいmagic。魔法のようなものはありません。
Stelios Adamantidis

11

これにより、静的型付き言語(CLR)がDLR(動的言語ランタイム)で実行されている動的言語(Python、rubyなど)と相互運用しやすくなります。MSDNを参照してください。

たとえば、次のコードを使用して、C#のXMLでカウンターをインクリメントできます。

Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);

DLRを使用すると、同じ操作の代わりに次のコードを使用できます。

scriptobj.Count += 1;

MSDNには次の利点があります。

  • 動的言語の.NET Frameworkへの移植を簡素化
  • 静的に型付けされた言語で動的機能を有効にします
  • DLRおよび.NET Frameworkの将来の利点を提供します
  • ライブラリとオブジェクトの共有が可能
  • 高速の動的ディスパッチと呼び出しを提供

詳細については、MSDNを参照してください。


1
また、動的に必要なVMへの変更により、実際には動的言語がより簡単になります。
Dykam 2010

2
@Dykam:VMに変更はありません。DLRは、.NET 2.0までは問題なく動作します。
イェルクWミッターク

@ヨルク、はい、変化があります。DLRは、VMが動的解決をサポートするように組み込まれているため、部分的に書き直されています。
Dykam 2010

私は少し楽観的でした、研究は変化がそれほど大きくなかったことを示しました。
Dykam 2010

4

使用例:

共有プロパティ 'CreationDate'を持つ多くのクラスを使用します。

public class Contact
{
    // some properties

    public DateTime CreationDate { get; set; }        
}

public class Company
{
    // some properties

    public DateTime CreationDate { get; set; }

}

public class Opportunity
{
    // some properties

    public DateTime CreationDate { get; set; }

}

'CreationDate'プロパティの値を取得するCommunメソッドを作成する場合、リフレクションを使用する必要があります。

    static DateTime RetrieveValueOfCreationDate(Object item)
    {
        return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
    }

「動的」の概念により、コードははるかにエレガントになります。

    static DateTime RetrieveValueOfCreationDate(dynamic item)
    {
        return item.CreationDate;
    }

7
ダックタイピング、いいね。ただし、これらがタイプである場合は、このためのインターフェースを使用する必要があります。
Kugel


2

コードの品質、IntelliSense、およびコンパイル時のバグ検出を破壊するために、RADおよびPythonの被害者によって主に使用されます。


皮肉な答えですが、簡単に本当です。構造体の宣言を回避するためだけに行われたのを見てきましたが、コードがすべて正常であれば機能しますが、チーズを移動するとすぐに予期しない方法でスタックが吹き飛ばされます。
AnthonyVO 2016

ええ、あなたは他の多くの言語機能を備えた古典的なコーナーカットを見るでしょう。ここでも見られるのは当然です。
Hawkeye4040

1

実行時に評価されるため、JavaScriptでできるようにタイプを好きなように切り替えることができます。これは合法です:

dynamic i = 12;
i = "text";

そして、必要に応じてタイプを変更できます。最後の手段として使用してください。それは有益ですが、生成されたILの面で多くのことが舞台裏で行われていると聞いたので、それはパフォーマンスの価格になる可能性があります。


7
それが「レギット」だとは言いたくない。それは確実にコンパイルされます。そのため、コンパイラーがコンパイルし、ランタイムがそれを実行するという意味では、「レジットコード」です。しかし、私が維持しているコードでその特定のコード(またはそれに似たもの)を見ることは絶対に避けたいです。
Lasse V. Karlsen、2010

6
もちろん、それは「ダイナミック」ではなく「オブジェクト」を使った「レギット」でした。ここでは、動的について興味深いことは何も示していません。
Eric Lippert、2010

オブジェクトの場合、そのメソッドを実際に呼び出すには、適切な型にキャストする必要があります...シグネチャを失います。コンパイルエラーなしでコードを任意のメソッドを呼び出すことができ、実行時にエラーが発生します。急いでタイプしていたのですが、ご指定いただけませんでした。そして、@ Lasse、私は同意するだろうし、おそらく私は動的をあまり使用しないだろう。
ブライアンメインズ2010

1
最後の手段の使用例は説明されていません
denfromufa

1

'動的'型変数の最適な使用例は、最近、ADO.NETでデータアクセス層を(SQLDataReaderを使用して)作成していて、コードが既に作成されたレガシストアドプロシージャを呼び出していたときでした。ビジネスロジックの大部分を含むレガシーストアドプロシージャが数百あります。私のデータアクセスレイヤーは、いくつかの操作を行うために、ある種の構造化データをC#ベースのビジネスロジックレイヤーに返す必要がありましたほとんどありません)。すべてのストアドプロシージャは、異なるデータセット(テーブル列)を返します。そのため、返されたデータを保持してBLLに渡すために数十のクラスまたは構造体を作成する代わりに、以下のコードを書いて、非常にエレガントで端正に見えます。

public static dynamic GetSomeData(ParameterDTO dto)
        {
            dynamic result = null;
            string SPName = "a_legacy_stored_procedure";
            using (SqlConnection connection = new SqlConnection(DataConnection.ConnectionString))
            {
                SqlCommand command = new SqlCommand(SPName, connection);
                command.CommandType = System.Data.CommandType.StoredProcedure;                
                command.Parameters.Add(new SqlParameter("@empid", dto.EmpID));
                command.Parameters.Add(new SqlParameter("@deptid", dto.DeptID));
                connection.Open();
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        dynamic row = new ExpandoObject();
                        row.EmpName = reader["EmpFullName"].ToString();
                        row.DeptName = reader["DeptName"].ToString();
                        row.AnotherColumn = reader["AnotherColumn"].ToString();                        
                        result = row;
                    }
                }
            }
            return result;
        }

0
  1. pythonnetを使用して、CPythonなどの動的言語を呼び出すことができます。

dynamic np = Py.Import("numpy")

  1. ジェネリックにdynamic数値演算子を適用する場合、ジェネリックをキャストできます。これにより、タイプセーフが提供され、ジェネリックの制限が回避されます。これは本質的に*アヒルの入力です:

T y = x * (dynamic)x、 どこ typeof(x) is T


0

dynamicタイピングのもう1つの使用例は、共分散または反変の問題が発生する仮想メソッドです。そのような例の1つは、Clone呼び出されたオブジェクトと同じタイプのオブジェクトを返す悪名高いメソッドです。この問題は静的な型チェックをバイパスするため、動的な戻りで完全に解決されるわけではありませんが、少なくともplainを使用するときは常に醜いキャストを使用する必要はありませんobject。そうでなければ、キャストは暗黙的になります。

public class A
{
    // attributes and constructor here
    public virtual dynamic Clone()
    {
        var clone = new A();
        // Do more cloning stuff here
        return clone;
    }
}

public class B : A
{
    // more attributes and constructor here
    public override dynamic Clone()
    {
        var clone = new B();    
        // Do more cloning stuff here
        return clone;
    }
}    

public class Program
{
    public static void Main()
    {
        A a = new A().Clone();  // No cast needed here
        B b = new B().Clone();  // and here
        // do more stuff with a and b
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.