2017-04-07 76 views
18

考慮在類庫下面的代碼省略參數不破的應用:庫更改委託簽名使用它

public class Service 
{ 
    public delegate string Formatter(string s1, string s2); 

    public void Print(Formatter f) 
    { 
     Console.WriteLine(f("a", "b")); 
    } 
} 

下面是一個使用它的控制檯應用程序:

static void Main(string[] args) 
{ 
    s = new Service(); 
    s.Print(Concat); 
} 

static string Concat(string s1, string s2) 
{ 
    return string.Format("{0}-{1}", s1, s2); 
} 

到目前爲止,它打印出「ab」,就像人們所期望的那樣。

現在,我改變類庫如下:

public class Service 
{ 
    public delegate string Formatter(string s1); 

    public void Print(Formatter f) 
    { 
     Console.WriteLine(f("a")); 
    } 
} 

即我從委託中刪除了一個參數。我只編譯類庫和覆蓋DLL坐在旁邊的控制檯應用程序(控制檯應用程序是重新編譯)。我期望這是庫中的重大變化,如果我執行應用程序,它會發現不匹配,導致一些運行時異常。

相比之下,當我運行該應用程序時,根本沒有例外,我得到驚人的輸出「-a」。當我調試時,我可以看到Concat方法(帶有2個參數)被調用,下面的調用堆棧級別顯示Print調用f(「a」)(一個參數),任何地方都沒有錯誤指示。最有趣的是,Concat中的s1爲空,s2爲「a」。

我還與到簽名不同的變化(添加參數,改變參數類型)大多具有相同的結果發揮各地。當我將s2的類型從字符串更改爲int時,我得到一個異常,但不是當Concat方法被調用時,而是當它試圖調用string.Format時。

我使用.NET框架的目標4.5.1和3.5,x86和x64試了一下。

任何人都可以回答這是否是預期的行爲或錯誤?這對我來說很危險。

+0

如果添加參數而不是刪除參數會發生什麼? –

+1

嘿,是的,我可以重現這種行爲;那...看起來像一個CLR bug?我同意這當然不直觀 –

+2

特別好奇的是,它甚至通過了IL檢查 - 「peverify」完全滿意它(儘管這可能不是在IL中完成的,需要推遲到運行時)。實質上,委託構造函數的'native int'參數(表示函數)未驗證匹配,看起來好像是 –

回答

5

這裏是一個更簡單的repro - 基本上,我使用委託類型(IL使用的)上的「底層」構造函數來傳遞具有錯誤簽名的方法目標,並且它可以工作細(我指的是它不拋出一個異常 - 它的行爲就像你的代碼):

using System; 

static class P 
{ 
    static void Main() 
    { 
     // resolve the (object, IntPtr) ctor 
     var ctor = typeof(Func<string, string>).GetConstructors()[0]; 

     // resolve the target method 
     var mHandle = typeof(P).GetMethod(nameof(Concat)) 
      .MethodHandle.GetFunctionPointer(); 
     object target = null; // because: static 

     // create delegate instance 
     var del = (Func<string, string>)ctor.Invoke(new object[] { target, mHandle }); 
     var result = del("abc"); 
     Console.WriteLine(result); // "-abc" 
    } 
    public static string Concat(string s1, string s2) 
    { 
     return string.Format("{0}-{1}", s1, s2); 
    } 
} 

這是不是一個真正的解釋。但是如果你想問更多的CLR專家,這可能會有所幫助!我會預期委託構造函數有關於目標是不正確大聲抱怨。

在猜測(純粹推測),它是一個例子:如果你通過IntPtr(native int),那麼你完全是你自己的 - 代碼做的最快的事情可能。雖然這看起來像是一個令人討厭的陷阱,但對於不小心!

至於爲什麼s2具有值和s1是空的:我,這是因爲堆棧建立向下(不起來),因此在兩個參數的方法,arg1是參數緊鄰到在堆棧上的前一個位置。當我們通過一個單一的值,而不是兩個,我們只把一個值之下,所以s2有一個值,而s1是不確定的(可能是從以前的代碼垃圾)。

+0

關於s1和s2的值我有相同的猜測。儘管添加參數s3也具有相同的結果(s1爲空,s2爲「a」),但對於堆棧佈局解釋,應該選取三個傳遞參數中的兩個。 – dzs

+0

有趣的是,向Concat添加一個參數(s3)(並在string.Format中使用它)會導致AccessViolationException。 – Evk