簡體   English   中英

更多關於C#中的隱式轉換運算符和接口(再次)

[英]More on implicit conversion operators and interfaces in C# (again)

好的。 我已經讀過這篇文章了,我對它如何適用於我的例子感到困惑(下圖)。

class Foo
{
    public static implicit operator Foo(IFooCompatible fooLike)
    {
        return fooLike.ToFoo();
    }
}

interface IFooCompatible
{
    Foo ToFoo();
    void FromFoo(Foo foo);
}

class Bar : IFooCompatible
{
    public Foo ToFoo()
    {
        return new Foo();   
    }

    public void FromFoo(Foo foo)
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Bar();
        // should be the same as:
        // var foo = (new Bar()).ToFoo();
    }
}

我已經徹底閱讀了我鏈接的帖子。 我已閱讀C#4規范的第10.10.3節。 給出的所有示例都涉及泛型和繼承,而上述情況則不然。

任何人都可以解釋為什么在這個例子的上下文中不允許這樣做

請不要以“因為規范說明”或僅引用規范的形式發布帖子。 顯然,規范不足以讓我理解,否則我就不會發布這個問題了。

編輯1:

據我所知這是不允許的,因為有反對它的規則。 我很困惑為什么不允許這樣做。

我理解這是不允許的,因為有規則反對它。 我很困惑為什么不允許這樣做。

一般規則是: 用戶定義的轉換不得以任何方式替換內置轉換。 有一些微妙的方法可以違反此規則涉及泛型類型,但您明確表示您對泛型類型方案不感興趣。

你不能,例如,使從用戶定義的轉換MyClassObject ,因為已經來自隱式轉換MyClassObject “內置”轉換將始終獲勝 ,因此允許您聲明用戶定義的轉換將毫無意義。

此外,您甚至無法進行用戶定義的隱式轉換來替換內置的顯式轉換。 你不能,例如,使由用戶定義的隱式轉換ObjectMyClass ,因為已經有一個內置的顯式轉換,從ObjectMyClass 對於代碼的讀者而言,讓您任意將現有的顯式轉換重新分類為隱式轉換,這簡直太令人困惑了。

特別是在涉及身份的情況下。 如果我說:

object someObject = new MyClass();
MyClass myclass = (MyClass) someObject;

那么我希望這意味着“ someObject實際上是MyClass類型,這是一個顯式的引用轉換,現在myclasssomeObject是引用相等的”。 如果你被允許說

public static implicit operator MyClass(object o) { return new MyClass(); }

然后

object someObject = new MyClass();
MyClass myclass = someObject;

這將是合法的 ,並且這兩個對象不具有引用相等性 ,這是奇怪的

我們已經有足夠的規則來取消您的代碼資格,從代碼轉換為未密封的類類型。 考慮以下:

class Foo { }
class Foo2 : Foo, IBlah { }
...
IBlah blah = new Foo2();
Foo foo = (Foo) blah;

這是有效的,並且有理由期望blahfoo是引用等於因為將Foo2強制轉換為其基類型Foo不會更改引用。 現在假設這是合法的:

class Foo 
{
    public static implicit operator Foo(IBlah blah) { return new Foo(); }
}

如果這是合法的,那么此代碼是合法的:

IBlah blah = new Foo2();
Foo foo = blah;

我們剛剛將派生類的實例轉換為其基類,但它們不是引用相等的。 這是奇怪和令人困惑的,因此我們將其視為非法。 您可能不會聲明這樣的隱式轉換, 因為它取代了現有的內置顯式轉換。

僅此一項,您不得通過任何用戶定義的轉換替換任何內置轉換的規則足以讓您無法創建帶有接口的轉換。

可是等等! 假設Foo密封的 再有就是之間沒有轉換IBlahFoo ,明確的或隱含的,因為不能可能由派生Foo2實現IBlah 在這種情況下,我們是否應該允許在FooIBlah之間進行用戶定義的轉換? 這種用戶定義的轉換不可能替換任何顯式或隱式的內置轉換。

不會。我們在規范的第10.10.3節中添加了一條額外的規則,該規則明確禁止任何用戶定義的轉換到接口或從接口轉換,無論是替換還是不替換內置轉換。

為什么? 因為有一個合理的期望,當一個人將一個值轉換一個接口時, 你正在測試所討論的對象是否實現了接口 ,而不是要求一個完全不同的對象來實現接口。 在COM術語中,轉換為接口是QueryInterface - “ 你實現了這個接口嗎? ” - 而不是QueryService - “ 你能找到我實現這個接口的人嗎?

類似地,人們有一個合理的期望,當一個人一個接口轉換時, 就會詢問該接口是否實際上是由給定目標類型的對象實現的 ,而不是要求一個與該對象完全不同的目標類型的對象。實現接口。

因此,進行轉換為接口或從接口轉換的用戶定義轉換始終是非法的。

然而, 泛型相當混亂,規范措辭不是很清楚,C#編譯器在其實現中包含許多錯誤 鑒於涉及泛型的某些邊緣情況,規范和實現都不正確,這對我(實施者)來說是一個難題。 我實際上正在與Mads合作澄清規范的這一部分,因為我將在下周在Roslyn實施它。 我將嘗試盡可能少地進行更改,但為了使編譯器行為和規范語言相互一致,可能需要少量數據。

您的示例的上下文,它將無法再次工作,因為隱式運算符已放置在接口上...我不確定您認為您的示例與您鏈接的示例有何不同,而不是您嘗試獲取一個具體的通過接口鍵入到另一個。

這里有關於connect的討論:

http://connect.microsoft.com/VisualStudio/feedback/details/318122/allow-user-defined-implicit-type-conversion-to-interface-in-c

Eric Lippert可能已經解釋了他在你的鏈接問題中說的原因:

接口值上的強制轉換始終被視為類型測試,因為對象實際上幾乎總是可能屬於該類型,並且確實實現了該接口。 我們不想否認你做一個廉價的代表保留轉換的可能性。

似乎與類型身份有關。 具體類型通過其層次結構相互關聯,因此可以在其中強制執行類型標識。 對於接口(以及其他被阻止的東西,例如dynamicobject ),類型標識變得毫無意義,因為任何人/每個人都可以被安置在這種類型下。

為什么這很重要,我不知道。

我更喜歡顯式代碼,它向我展示我試圖從另一個IFooCompatible獲取Foo ,因此轉換例程采用T where T : IFooCompatible返回Foo

對於你的問題,我理解討論的重點,但是我的諷刺反應是,如果我在野外看到像Foo f = new Bar() ,我很可能會重構它。


替代解決方案:

不要把這個布丁搗碎了:

Foo f = new Bar().ToFoo();

您已經暴露了Foo兼容類型實現接口以實現兼容性的想法,請在您的代碼中使用它。


鑄造與轉換:

在鑄造與轉換之間也很容易划線。 轉換意味着類型信息在您所投射的類型之間是不可變的,因此在這種情況下,轉換不起作用:

interface IFoo {}
class Foo : IFoo {}
class Bar : IFoo {}

Foo f = new Foo();
IFoo fInt = f;
Bar b = (Bar)fInt; // Fails.

Casting理解類型層次結構,並且fInt的引用不能轉換為Bar因為它實際上是Foo 您可以提供一個用戶定義的運算符來提供:

public static implicit operator Foo(Bar b) { };

在您的示例代碼中執行此操作,但這開始變得愚蠢。

另一方面,轉換完全獨立於類型層次結構。 它的行為完全是任意的 - 你編寫你想要的代碼。 這是你實際上的情況,將一個Bar轉換為Foo ,你碰巧用IFooCompatible標記可轉換的項目。 該接口不會使不同的實現類的轉換合法化。


至於為什么在用戶定義的轉換運算符中不允許接口:

為什么我不能使用帶顯式運算符的接口?

簡短版本是不允許的,以便用戶可以確定引用類型和接口之間的轉換是成功的,當且僅當引用類型實際實現該接口時,並且當發生轉換時實際引用相同的對象時。

好的,這是一個我為什么相信限制的例子:

class Foo
{
    public static implicit operator Foo(IFooCompatible fooLike)
    {
        return fooLike.ToFoo();
    }
}

class FooChild : Foo, IFooCompatible
{
}

...

Foo foo = new FooChild();
IFooCompatible ifoo = (IFooCompatible) foo;

編譯器應該做什么,以及執行時應該怎么做? foo 已經引用了IFooCompatible的實現,所以從這個角度來看它應該只是使它成為引用轉換 - 但是編譯器不知道是這種情況,所以它實際上只是調用隱式轉換嗎?

懷疑基本邏輯是:不允許操作員定義哪個操作符可能與基於執行時類型的已經有效的轉換沖突。 很高興從表達式到目標類型完全零或一個可能的轉換。

(編輯:亞當的答案聽起來像是在談論幾乎相同的事情 - 隨意將我的答案視為他的一個例子:)

這里可能有用的是.net提供一種“干凈”的方式來將接口與靜態類型相關聯,並且在接口類型上有各種類型的操作映射到靜態類型上的相應操作。 在某些情況下,這可以通過擴展方法來完成,但這既丑陋又有限。 將接口與靜態類相關聯可以提供一些顯着的優勢:

  1. 目前,如果接口希望為消費者提供多個功能的重載,則每個實現必須實現每個過載。 將靜態類與接口配對,並允許該類以擴展方法的方式聲明方法將允許類的使用者使用靜態類提供的重載,就像它們是接口的一部分一樣,而不需要實現者提供它們。 這可以通過擴展方法完成,但需要在使用者端手動導入靜態方法。
  2. 在許多情況下,接口將具有與其非常強烈關聯的一些靜態方法或屬性(例如,“Enumerable.Empty”)。 能夠為接口使用相同的名稱和相關屬性的“類”似乎比為兩個目的使用單獨的名稱更清晰。
  3. 它將提供支持可選接口成員的途徑; 如果成員存在於接口但不存在實現,則vtable槽可以綁定到靜態方法。 useful feature, since it would allow interfaces to be extended without breaking existing implementations. 這將是一個有用的功能,因為它將允許擴展接口而不會破壞現有的實現。

鑒於遺憾的是,這樣的特性只存在於使用COM對象所必需的范圍內,我能想到的最好的替代方法是定義一個結構類型,它保存一個接口類型的成員,並通過充當代理來實現接口。 。 從接口到結構的轉換不需要在堆上創建額外的對象,並且如果函數提供接受該結構的重載,則它們可以以其凈結果將是保值的方式轉換回接口類型。而不是需要拳擊。 不幸的是,將這樣的結構傳遞給使用接口類型的方法將需要裝箱。 可以通過讓struct的構造函數檢查傳遞給它的接口類型對象是否是該結構的嵌套實例來限制裝箱的深度,如果是這樣,則打開一層裝箱。 這可能有點icky,但在某些情況下可能會有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM