为啥要转换为接口?

Posted

技术标签:

【中文标题】为啥要转换为接口?【英文标题】:Why cast to an interface?为什么要转换为接口? 【发布时间】:2010-10-11 19:22:39 【问题描述】:

在 Jesse Liberty 的 Programming C# (p.142) 中,他提供了一个将对象强制转换为接口的示例。

 interface IStorable
 
    ...
 

 public class Document : IStorable
 
    ...
 

 ...
 IStorable isDoc = (IStorable) doc;  
 ...

这有什么意义,特别是如果对象的类实现了接口?

EDIT1:澄清一下,我感兴趣的是强制转换的原因(如果有的话)不是实现接口的原因。此外,这本书是他 2001 年的第一版(基于 C#1,因此该示例可能与更高版本的 C# 没有密切关系)。

EDIT2:我在代码中添加了一些上下文

【问题讨论】:

我的第一个猜测是显式的用户定义转换,但实际上根据 (6.4.1) 你不能定义显式的用户定义转换到接口。 我在最新版本中找到了等效部分,但看起来有些不同。 @Jon - 感谢您查找 你的意思是为什么 explicit 强制转换为一个接口,还是你的意思是“为什么还要费力地转换为接口?” 我的意思是为什么 explicit 演员表,它是必要的/有用的/多余的吗? 【参考方案1】:

显式转换为接口的主要原因是接口的成员是否显式实现(即具有InterfaceName.InterfaceMemberName 形式的完全限定名称)。这是因为当您使用接口名称完全限定它们时,这些成员实际上并不是实现类 API 的一部分。您可以通过转换到界面来访问它们。

这是一个您可以按原样运行的示例:

using System;

public interface ISomethingDoer 
    void DoSomething();


public class ThingA : ISomethingDoer 

    public void DoSomething()
        Console.WriteLine("ThingA did it!");
    


public class ThingB : ISomethingDoer 
    
    // This is implemented explicitly by fully-qualifying it with the interface name
    // Note no 'scope' here (e.g. public, etc.)
    void ISomethingDoer.DoSomething() 
        Console.WriteLine("ThingB did it!");
    


public static class Runner 

    public static void Main()

        var a = new ThingA();
        a.DoSomething(); // Prints 'ThingA did it!'

        var b = new ThingB();
        b.DoSomething(); // NOTE: THIS WILL NOT COMPILE!!!

        var bSomethingDoer = (ISomethingDoer)b;
        bSomethingDoer.DoSomething(); // Prints 'ThingB did it!'
    

HTH!

【讨论】:

【参考方案2】:

如果没有更多的上下文,这很难说。如果变量doc 被声明为实现接口的类型,则强制转换是多余的。

您正在阅读这本书的哪个版本?如果是“Programming C# 3.0”,我今晚在家的时候看看。

编辑:正如我们目前在答案中看到的,这里有三个潜在的问题:

为什么要在问题中显示的语句中进行转换? (答案:如果doc 是适当的编译时类型,则不必这样做) 为什么显式转换为已实现的接口或基类是合适的? (答案:如另一个答案所示的显式接口实现,也是为了在将强制转换值作为参数传递时选择不太具体的重载。) 为什么要使用界面? (答案:使用接口类型意味着您以后不太容易受到具体类型更改的影响。)

【讨论】:

"(IStorable)" 演员表还通过明确声明 "doc" 不是 "IStorable" 对象,而是可以转换为 "IStorable" 的东西来提高可读性。虽然是多余的,但它为以后可能阅读代码的任何人添加了这条信息。 我看不出这是多么有用的信息——事实上,它暗示(对我而言)doc变量是一种需要执行的类型——时间检查(听起来好像不是这里的情况)。这是误导。 "(答案:如果 doc 是适当的编译时类型,则不必这样做)"。只有一个字母,但它使世界变得不同。【参考方案3】:

为了允许代码片段之间的最大解耦...

请参阅以下文章了解更多信息: Interfaces

【讨论】:

【参考方案4】:

强制转换为接口的最佳原因是,如果您正在编写针对对象的代码并且您不知道它们是什么具体类型并且您不想这样做。

如果您知道您可能会遇到实现特定接口的对象,那么您可以从对象中获取值,而无需知道该对象的具体类。此外,如果您知道某个对象实现了给定接口,则该接口可能会定义您可以执行的方法,以便对对象执行某些操作。

这是一个简单的例子:

public interface IText

   string Text  get; 


public interface ISuperDooper

   string WhyAmISuperDooper  get; 


public class Control

   public int ID  get; set; 


public class TextControl : Control, IText

   public string Text  get; set; 


public class AnotherTextControl : Control, IText

   public string Text  get; set; 


public class SuperDooperControl : Control, ISuperDooper

   public string WhyAmISuperDooper  get; set; 


public class TestProgram

   static void Main(string[] args)
   
      List<Control> controls = new List<Control>
               
                   new TextControl
                       
                           ID = 1, 
                           Text = "I'm a text control"
                       ,
                   new AnotherTextControl
                       
                           ID = 2, 
                           Text = "I'm another text control"
                       ,
                   new SuperDooperControl
                       
                           ID = 3, 
                           WhyAmISuperDooper = "Just Because"
                       
               ;

       DoSomething(controls);
   

   static void DoSomething(List<Control> controls)
   
      foreach(Control control in controls)
      
         // write out the ID of the control
         Console.WriteLine("ID: 0", control.ID);

         // if this control is a Text control, get the text value from it.
         if (control is IText)
            Console.WriteLine("Text: 0", ((IText)control).Text);

         // if this control is a SuperDooperControl control, get why
         if (control is ISuperDooper)
            Console.WriteLine("Text: 0", 
                ((ISuperDooper)control).WhyAmISuperDooper);
      
   

运行这个小程序会给你以下输出:

ID:1

文本:我是一个文本控件

ID:2

文本:我是另一个文本控件

ID:3

文字:只是因为

请注意,我不必在 DoSomething 方法中编写任何代码,这些代码要求我了解我正在处理的所有作为具体对象类型的对象的任何信息。我唯一知道的是我正在处理至少是 Control 类的实例的对象。然后我可以使用该界面找出他们可能还有什么。

有上百万种不同的原因可以让您在对象上使用这种方法,但它为您提供了一种访问对象的松散方式,而无需确切知道它是什么。

想想世界上所有的信用卡,每个公司都有自己的信用卡,但是界面是一样的,所以每个读卡器都可以刷一张符合标准的卡。类似于接口的使用。

【讨论】:

【参考方案5】:

这里有很多很好的答案,但我真的不认为他们能回答为什么你真的想使用最严格的界面。

原因不涉及您最初的编码,它们涉及您下次访问或重构代码时 - 或者其他人这样做时。

假设您想要一个按钮并将其放置在您的屏幕上。您正在获取传入或来自另一个函数的按钮,如下所示:

Button x=otherObject.getVisibleThingy();
frame.add(x);

你碰巧知道 VisibleThingy 是一个按钮,它返回一个按钮,所以这里的一切都很酷(不需要强制转换)。

现在,假设您重构 VisibleThingy 以返回一个切换按钮。您现在必须重构您的方法,因为您对实现了解太多。

由于您只需要 Component 中的方法(按钮和 Toggle 的父级,这可能是一个接口——对于我们的目的来说几乎相同),如果您像这样编写第一行:

Component x=(Component)otherObject.getVisibleThingy();

您不必重构任何东西——它会奏效的。

这是一个非常简单的案例,但它可能要复杂得多。

所以我想总结一下,界面是一种“查看”对象的特定方式——就像通过过滤器查看它......你只能看到一些部分。如果您可以足够地限制您的视图,则该对象可以“变形”在您的特定视图后面,并且不会影响您当前世界中的任何东西——这是一个非常强大的抽象技巧。

【讨论】:

【参考方案6】:

如前所述,选角是多余的,没有必要。但是,它是一种更明确的编码形式,有助于初学者理解。

在入门教材中,最好是显式地行动,而不是让编译器隐式地做事,这样对初学者来说会比较混乱。

“doc”不是“IStorable”类型,因此初学者看到它被分配给 isDoc 会感到困惑。通过显式转换,作者(本书和代码的作者)说文档可以转换为 IStorable 对象,但它与 IStorable 对象不同。

【讨论】:

【参考方案7】:

真正需要强制转换的原因只有一个:当 doc 是实现 IStorable 的实际对象的基本类型时。让我解释一下:

public class DocBase

  public virtual void DoSomething()
  

  


public class Document : DocBase, IStorable

  public override void DoSomething()
  
    // Some implementation
    base.DoSomething();
  

  #region IStorable Members

  public void Store()
  
    // Implement this one aswell..
    throw new NotImplementedException();
  

  #endregion


public class Program

  static void Main()
  
    DocBase doc = new Document();
    // Now you will need a cast to reach IStorable members
    IStorable storable = (IStorable)doc;
  


public interface IStorable

  void Store();

【讨论】:

谢谢,但这让我有点困惑。为什么要在创建 Document 对象时使用基本类型 DocBase? 一个原因可能是您使用工厂来构造对象并将对象作为其基本类型返回。 可能有多种原因。我看到的主要是通过实现由 3rd 方提供并且没有指定接口的基类。在这种情况下,“DocBase”类来自不同的库,并在您自己的代码中使用接口实现。 -1,没有解释为什么在将编译时已知的类型分配给 IStorable 以代替 EDIT2 实现 IStorable 变量时使用强制转换。 " // 现在你需要一个演员来接触 IStorable 成员" -- 这是上面代码中写的,但是:仍然不能使用以下内容接触 IStorable 成员:doc.商店()??【参考方案8】:

我不确定书中给出的示例是在什么背景下给出的。但是,您通常可以将对象类型强制转换为接口以实现多重继承。我已经给出了下面的例子。

public interface IFoo

     void Display();

public interface IBar

     void Display();


public class MyClass : IFoo, IBar

    void IBar.Display()
    
        Console.WriteLine("IBar implementation");
    
    void IFoo.Display()
    
        Console.WriteLine("IFoo implementation");
    


public static void Main()

    MyClass c = new MyClass();
    IBar b = c as IBar;
    IFoo f = c as IFoo;
    b.Display();
    f.Display();
    Console.ReadLine();

这会显示

IBar 实施 IFoo 实现

【讨论】:

【参考方案9】:

关键是,对象(你从哪里得到的?)可能没有实现接口,在这种情况下会抛出异常,可以捕获和处理。当然你可以使用 "is" 操作符进行检查,使用 "as" 操作符进行转换,而不是 C 风格的转换。

【讨论】:

有人能解释一下为什么你投了反对票吗?谢谢。【参考方案10】:

doc 对象可能属于显式实现 IStorable 成员的类型,而不是将它们添加到类主接口(即,它们只能通过接口调用)。

实际上“转换”(使用 (T) 语法)没有任何意义,因为 C# 会自动处理向上转换(转换为父类型)(例如,与 F# 不同)。

【讨论】:

【参考方案11】:

因为您想将自己限制为仅使用接口提供的方法。如果您使用该类,则可能会(无意中)调用不属于接口的方法。

【讨论】:

你能不能不做IStorable isDoc = doc;如果 doc 是 IStorable 的实现类的实例?演员阵容似乎是多余的,除非我将 Java 和 C# 混合在一起。 (很遗憾,我有一段时间没学过 C#了)。 @Devin:是的,只要将 doc 声明为实现接口的类型,强制转换是多余的。 引用本身不会限制你调用接口外的任何方法吗?【参考方案12】:

如果对象显式实现了接口 (public void IStorable.StoreThis(...)),则强制转换是实际到达接口成员的最简单方法。

【讨论】:

还有其他方法可以访问显式接口成员吗? 这是访问显式实现的接口成员的唯一方法。显式实现的原因是您希望保持类更干净,因为可能很少使用该实现。

以上是关于为啥要转换为接口?的主要内容,如果未能解决你的问题,请参考以下文章

为啥可以将切片分配给空接口但不能将其强制转换为相同的空接口

为啥要发生字节序转换 ? ? ?

为啥要将 Java 接口方法声明为抽象的?

这段代码里的Object类型为啥能被强制转换为Comparable接口类型呢?

PHP实现chrome表单请求数据转换为接口使用的json数据

█■为啥要用实现接口的类实例化接口呢?