C#コンパイラが汎用メソッドをタイプ推定として呼び出す奇妙な問題

5563 ワード

汎用型は.NETプラットフォーム上の重要な機能である汎用型は「不確定」なタイプである.C#3.0では、タイプ推定に力を入れています.タイプ推定が欠けている場合、汎用メソッドの呼び出しなどのC#の機能の大部分は、Lambda式の可用性の大部分を失います.複雑すぎるため、誰も使いません(ここのJavaコードを覚えていますか).
タイプ推定の機能は、コンパイラがコードではなくコンテキストから汎用パラメータの特定のタイプを自動的に認識できるようにすることです.しかし、C#のコード推定はかなり不完全であることがわかります.たとえば、このようなコードを用意しました.
public interface ISome
{
    int Method(string arg);
}

public class Mock
{
    public void Setup(Func func) { }
}

public static class It
{
    public static T IsAny() { return default(T); }
}

Moqフレームワークに詳しい友人は、このコードがMoqの準備コードに近いことに気づくに違いありません(もちろん、ここでは依頼ですが、Moqは式ツリーを使用しています).そこで、私たちはこのようなコードを書くことを望んでいます.
var mockSome = new Mock<ISome>();
mockSome.Setup(s => s.Method(It.IsAny()));

ISomeインタフェースのMethodメソッド署名が完全に決定されたため,コンパイラはSetupメソッドとIsAnyメソッドの汎用パラメータを完全に推定できる.しかし、そうすれば、C#コンパイラはこのようなエラーメッセージを与えます.
The type arguments for method 'It.IsAny()' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Itを指定する必要があります.IsAny法の汎用パラメータ.
実際,これを指定するだけでは不十分であり,Setup法の汎用パラメータを引き続き要求している.
そのため、私たちはこのように書かなければなりません.
var mockSome = new Mock<ISome>();

    
    
  
  
   
   
     
     mockSome.Setup<
     
     
   
   int>(s => s.Method(
     
     
   
   It.IsAny<
     
     
   
   string>()));
    
    
  
  
mockSome.Setup(s => s.Method(It.IsAny<string>()));

今はintかstringで問題はないかもしれませんが、IGrouping>という強力なタイプに出会ったら、私と同じように泣きたいと思います.あなたもこのような問題に遭遇したことがあると信じています.
多くの場合、このような問題に遭遇することはないと思っていましたが(実際には普段は確かに爽やかに使われています)、私はさっきうっかり別の奇妙な状況にぶつかって、それは私に十数分の時間を費やして、最後に他の人のヒントの下でやっと問題の所在を発見しました.私が書いた汎用的な拡張方法の一つです.
public static TDictionary RemoveKeys(
    this TDictionary source, IEnumerable keys)
    where TDictionary : IDictionary
{
    foreach (var key in keys)
    {
        source.Remove(key);
    }

    return source;
}

この拡張方法の役割は、1つのIDictionaryオブジェクトからkey対応のコンテンツの一部を削除することです.コンテンツ自体は簡単ですが、使用中に問題が発生しました.
var values = new RouteValueDictionary();
values.RemoveKeys(...);

RouteValueDictionaryはIDictionaryインタフェースを実現した辞書オブジェクトなので、すべて正常なはずですが、コンパイラは私に問題を教えてくれました.
'System.Web.Routing.RouteValueDictionary' does not contain a definition for 'RemoveKeys' and no extension method 'RemoveKeys' accepting a first argument of type 'System.Web.Routing.RouteValueDictionary' could be found.
このエラープロンプトは以前とは異なり,RemoveKeys法が欠けていることを示し,汎用パラメータの補完を要求していない.しかし、汎用パラメータを補完すると問題ありません.
values.RemoveKeys<RouteValueDictionary, string, object>(...);

しかし、このようなコードを書きたいですか?
そこで、最後にもう一つの方法を補足しなければなりません.「汎用パラメータの強制指定」の方法を誤って迂回しました.
public static IDictionary RemoveKeys(
    this IDictionary source, IEnumerable keys)
{
    foreach (var key in keys)
    {
        source.Remove(key);
    }

    return source;
}

この方法は汎用パラメータの制限条件としてではなくIDictionaryタイプを直接使用している--問題はこのように解決された.なぜか聞かないでください.私も知りません.
この問題は表面的に解決されたが,新しい方法は辞書タイプにも作用するが,新しい方法はIDictionaryタイプのみを返すことができ,古いメソッドでは元のタイプ(RouteValueDictionaryなど)が返されます.元のタイプを返すとFluent Interfaceが利用でき、コードの作成が便利になります.
これはC#コンパイラのバグでしょうが、他の友達が出会ったことがあるかどうか分かりません.C#コンパイラの半吊り子のタイプ推定特性は、常に「サプライズ」を与えてくれます.また、HaskellやF#などの関数言語のタイプ推定が最も強力です.コードを書くときは、F#のタイプ推定機能が強すぎてコードの可読性が低下するだけで(そのため、「アクティブ」にタイプを加えることもある)、ある「誰でもタイプが見える」場合ではなく、コンパイラが盲目のように具体的なタイプを提供する必要はありません.