2012-11-26 36 views
10

見代碼段除了泛型類之外,還有其他方法可以調用結構的接口方法嗎?

public interface I0 
{ 
    void f0(); 
} 
public struct S0:I0 
{ 
    void I0.f0() 
    { 

    } 
} 
public class A<E> where E :I0 
{ 
    public E e; 
    public void call() 
    { 
     e.f0(); 
    } 
} 

這裏爲呼叫IL代碼()

.maxstack 8 
L_0000: ldarg.0 
L_0001: ldflda !0 Temp.A`1<!E>::e 
L_0006: constrained !E 
L_000c: callvirt instance void Temp.I0::f0() 
L_0011: ret 

參見constrained

的約束前綴參考,也可用於接口方法上調用值類型,因爲實現接口方法的值類型方法可以使用MethodImpl進行更改。如果未使用約束前綴,則編譯器將被迫在編譯時選擇要綁定哪個值類型的方法。使用約束前綴允許MSIL在運行時綁定到實現接口方法的方法,而不是在編譯時。

這意味着它會調用一個包含f0接口方法代碼的方法,而不用裝箱結構。

做任何其他方式調用無拳擊接口方法存在如上C#中的GenericClass?

+0

(我想這應該是最好的評論:))請參閱http://stackoverflow.com/questions/3032750/structs-interfaces-and-boxing – Matthias

+0

感謝您的回覆。我修改了標題:除了在泛型中如何調用? – Vince

+1

對我而言,我沒有閱讀完整的文章,因爲從標題我預計不同的東西:)對你的問題:我*猜*不。 – Matthias

回答

15

這取決於...你說具體你不想通用類...唯一的其他選擇是一種通用的方法一個非通用類。唯一其他時間,你可以讓編譯器發出constrained電話是:如果你在一個structToString()GetHashCode()Equals()(從object),這是因爲它們然後constrained - 如果structoverride他們將call;如果它不是override,他們將是callvirt。這就是爲什麼你應該總是override這3個任何struct; p但我離題了。一個簡單的例子是一個帶有一些靜態方法的工具類 - 擴展方法將是一個理想的例子,因爲您還可以獲得編譯器會自動在公共/隱式API和擴展/顯式API之間切換的優勢,而無需您需要更改代碼。例如,下面的(這說明兩者的隱性和顯性的實現)沒有拳擊,一個call和一個constrained + callvirt,這將在JIT通過call實現:

using System; 
interface IFoo 
{ 
    void Bar(); 
} 
struct ExplicitImpl : IFoo 
{ 
    void IFoo.Bar() { Console.WriteLine("ExplicitImpl"); } 
} 
struct ImplicitImpl : IFoo 
{ 
    public void Bar() {Console.WriteLine("ImplicitImpl");} 
} 
static class FooExtensions 
{ 
    public static void Bar<T>(this T foo) where T : IFoo 
    { 
     foo.Bar(); 
    } 
} 
static class Program 
{ 
    static void Main() 
    { 
     var expl = new ExplicitImpl(); 
     expl.Bar(); // via extension method 
     var impl = new ImplicitImpl(); 
     impl.Bar(); // direct 
    } 
} 

而這裏的關鍵位IL:

擴展方法
.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    .maxstack 1 
    .locals init (
     [0] valuetype ExplicitImpl expl, 
     [1] valuetype ImplicitImpl impl) 
    L_0000: ldloca.s expl 
    L_0002: initobj ExplicitImpl 
    L_0008: ldloc.0 
    L_0009: call void FooExtensions::Bar<valuetype ExplicitImpl>(!!0) 
    L_000e: ldloca.s impl 
    L_0010: initobj ImplicitImpl 
    L_0016: ldloca.s impl 
    L_0018: call instance void ImplicitImpl::Bar() 
    L_001d: ret 
} 
.method public hidebysig static void Bar<(IFoo) T>(!!T foo) cil managed 
{ 
    .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() 
    .maxstack 8 
    L_0000: ldarga.s foo 
    L_0002: constrained. !!T 
    L_0008: callvirt instance void IFoo::Bar() 
    L_000d: ret 
} 

一個缺點,雖然是,它是做堆棧上的struct的額外副本(見ldloc.0),其中威力是一個問題如果它超大,或者它是一種變異方法(你應該避免)。如果是這種情況,ref參數是有幫助的,但請注意,擴展方法不能有ref this參數 - 所以你不能用擴展方法來做到這一點。但考慮:

Bar(ref expl); 
Bar(ref impl); 

有:

static void Bar<T>(ref T foo) where T : IFoo 
{ 
    foo.Bar(); 
} 

是:

L_001d: ldloca.s expl 
L_001f: call void Program::Bar<valuetype ExplicitImpl>(!!0&) 
L_0024: ldloca.s impl 
L_0026: call void Program::Bar<valuetype ImplicitImpl>(!!0&) 

有:

.method private hidebysig static void Bar<(IFoo) T>(!!T& foo) cil managed 
{ 
    .maxstack 8 
    L_0000: ldarg.0 
    L_0001: constrained. !!T 
    L_0007: callvirt instance void IFoo::Bar() 
    L_000c: ret 
} 

仍然沒有拳擊,但現在我們也絕不照搬結構,即使是顯式的情況。

+0

完美!付出我的尊重! – Vince

0

由於接口被視爲引用類型,因此無法在接口所引用的結構上調用方法,而無需首先將底層結構框起來。

當使用強制實現接口的類型的泛型方法時,C#編譯器只是簡單地將實際的實現細節和調用約定提升到運行時。幸運的是,C#編譯器足夠聰明,可以指示JIT編譯器受到的類型確實實現了接口X並且可能是一個結構體。有了這些信息,JIT編譯器就可以計算出如何調用接口X所聲明的方法Y.

上述技巧不適用於非泛型方法調用,因爲JIT編譯器找不到實用的方法當X是非密封的類或接口時,由參數X表示的實際類型。因此,如果傳遞的接口表示的類型是非密封類和處理結構和密封類的直接方法調用,則C#編譯器無法生成處理查找表的JIT。

當使用動態時,理論上可以防止裝箱,但是引入DLR的性能損失很可能沒有任何好處。

+2

DLR是非常拳擊重的......實際上,'dynamic'實現*本質上*作爲'object'。不確定你最後一行是否站起來,至少沒有進一步的解釋/細節。 –

相關問題