静的メソッドがインスタンスメソッドを呼び出す場所で、C#コンパイラがコードをエラーにしないのはなぜですか?


110

次のコードにはFoo()、インスタンスメソッドを呼び出す静的メソッドがありますBar()

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

エラーなしでコンパイルされます*が、実行時にランタイムバインダー例外が生成されます。これらのメソッドへの動的パラメーターを削除すると、期待どおりにコンパイラエラーが発生します。

では、なぜ動的パラメータを使用すると、コードをコンパイルできるのでしょうか。ReSharperもエラーとして表示しません。

編集1: * Visual Studio 2008で

編集2:sealedサブクラスに静的Bar(...)メソッドが含まれる可能性があるため、追加されました。シールドされたバージョンでさえ、実行時にインスタンスメソッド以外のメソッドを呼び出すことができない場合にコンパイルされます。


8
非常に良い質問の+1
cuongle

40
これは、Eric-Lippertの質問です。
Olivier Jacot-Descombes

3
私はジョン・スキートがこれと同様に何をすべきか知っていると確信しています;)@ OlivierJacot-Descombes
Thousand

2
@オリビエ、ジョン・スキートはおそらくコードをコンパイルしたかったので、コンパイラはそれを許可します:
Mike Scott

5
これは、dynamic本当に必要な場合以外は使用しない理由のもう1つの例です。
2012年

回答:


71

更新:以下の回答は、C#7.3(2018年5月)が導入される前の 2012年に書かれました。ではC#7.3の新機能、セクションの改善過負荷候補、項目1、オーバーロード解決規則は、非静的過負荷が早期に破棄されるように変更されているかを説明します。したがって、以下の回答(およびこの質問全体)は、現時点ではほとんど歴史的な興味しかありません!


(C#7.3より前:)

何らかの理由で、オーバーロードの解決では、静的か非静的かをチェックする前に常に最適な一致を見つけます。すべての静的タイプでこのコードを試してください:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

最適なオーバーロードはstring。しかし、ねえ、それはインスタンスメソッドなので、コンパイラは(2番目に優れたオーバーロードをとる代わりに)エラーを出します。

加えて:dynamic元の質問の例の説明は、一貫性を保つために、型が動的である場合、最初に最適なオーバーロードを見つけることだと思います(パラメーター番号とパラメーターの型のみをチェックするなど、静的か非かをチェックします) -static)、そして次に静的をチェックします。しかし、それは静的チェックが実行時まで待機する必要があることを意味します。したがって、観察された動作。

後期追加:彼らがこのおかしな命令を行うことを選択した理由の背景は、Eric Lippertによるこのブログ投稿から推測できます。


元の質問にはオーバーロードはありません。静的な過負荷を示す回答は関係ありません。私がそれを書いていないので、「もしあなたがこれを書いたなら...」と答えることは無効です:-)
Mike Scott

5
@MikeScott C#でのオーバーロードの解決は常に次のようになることを説得しようとします:(1)静的/非静的を無視して最適な一致を見つけます。(2)これで、使用するオーバーロードがわかったので、静的かどうか確認します。このためdynamic、言語で導入されたとき、C#の設計者は「(2)コンパイル時はdynamic式の場合は考慮しない」と言っていたと思います。したがって、ここでの目的は、実行時までインスタンスと静的のチェックを行わない理由を考え出すことです。このチェックはbinding-timeで行われると思います。
Jeppe Stig Nielsen

十分に公平ですが、この場合、コンパイラがインスタンスメソッドの呼び出しを解決できない理由はまだ説明されていません。言い換えると、コンパイラーが解決を行う方法は単純化されています-呼び出しを解決できない可能性がない私の例のような単純なケースを認識しません。皮肉なことに、動的パラメーターを持つ単一のBar()メソッドを持つことで、コンパイラーはその単一のBar()メソッドを無視します。
マイクスコット

45
私はC#コンパイラのこの部分を書きましたが、Jeppeは正しいです。これに投票してください。オーバーロードの解決は、特定のメソッドが静的メソッドであるかインスタンスメソッドであるかをチェックする前に行われます。この場合、オーバーロードの解決をランタイムに延期します。したがって、静的/インスタンスチェックもランタイムまで延期します。さらに、コンパイラーは、完全ではない動的エラーを静的に見つけるために「最善の努力」をします。
Chris Burrows

30

Fooには動的なパラメーター「x」があります。つまり、Bar(x)は動的な式です。

Exampleが次のようなメソッドを持つことは完全に可能です。

static Bar(SomeType obj)

その場合、正しいメソッドが解決されるので、ステートメントBar(x)は完全に有効です。インスタンスメソッドBar(x)があるという事実は無関係ではなく、考慮もされていません。定義により、Bar(x)は動的な式であるため、解決は実行時まで延期されています。


14
しかし、インスタンスのBarメソッドを削除すると、コンパイルされなくなります。
ジャスティンハーベイ、

1
@ジャスティン興味深い-警告?またはエラー?どちらの方法でも、メソッドグループまでしか検証されず、完全なオーバーロードの解決はランタイムに委ねられます。
マークグラベル

1
@Marc、別のBar()メソッドがないので、あなたは質問に答えていません。オーバーロードのないBar()メソッドは1つしかないので、これを説明できますか?他のメソッドを呼び出すことができない方法があるのに、なぜランタイムに延期するのですか?またはありますか?注:コードを編集してクラスをシールしましたが、まだコンパイルされています。
マイクスコット

1
@mikeなぜランタイムに延期するか:動的な意味
Marc Gravell

2
@マイク不可能は重要ではありません。重要なのは、それが必要かどうかです。ダイナミックの全体的なポイントは、それがコンパイラの仕事ではないということです。
Marc Gravell

9

「動的」式は実行時にバインドされるため、正しいシグネチャまたはインスタンスメソッドで静的メソッドを定義すると、コンパイラはそれをチェックしません。

「正しい」方法は実行時に決定されます。コンパイラーは、実行時にそこに有効なメソッドがあるかどうかを知ることができません。

「動的」キーワードは動的およびスクリプト言語用に定義されており、メソッドは実行時でもいつでも定義できます。バカバカしく

メソッドがインスタンスにあるため、intは処理するが文字列は処理しないサンプルを次に示します。

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

処理できなかったすべての「間違った」呼び出しを処理するメソッドを追加できます

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

あなたの例の呼び出しコードはExample.Foo(...)ではなくExample.Bar(...)であるべきではありませんか?あなたの例ではFoo()は無関係ではありませんか?私はあなたの例を本当に理解していません。静的汎用メソッドを追加すると問題が発生するのはなぜですか?回答を編集して、オプションとして指定するのではなく、そのメソッドを含めることができますか?
マイクスコット

しかし、私が投稿した例には単一のインスタンスメソッドのみがあり、オーバーロードがないため、コンパイル時に、解決できる可能性のある静的メソッドがないことがわかります。少なくとも1つを追加した場合にのみ、状況が変化し、コードが有効になります。
マイクスコット

しかし、この例にはまだ複数のBar()メソッドがあります。私の例には1つのメソッドしかありません。したがって、静的なBar()メソッドを呼び出す可能性はありません。呼び出しはコンパイル時に解決できます。
マイクスコット

@Mikeは!=です。ダイナミックでは、そうする必要はありません
マークグラベル

@MarcGravell、明確にしてください?
マイクスコット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.