为什么要转换为界面?

时间:2009-03-06 17:35:14

标签: c# interface casting

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

 interface IStorable
 {
    ...
 }

 public class Document : IStorable
 {
    ...
 }

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

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

EDIT1:澄清一下,我对推理的原因(如果有的话)实施接口的原因感兴趣。此外,这本书是他的2001年第一版(基于C#1,所以这个例子可能与以后版本的C#没有密切关系。)

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

12 个答案:

答案 0 :(得分:21)

因为您希望仅限于接口提供的方法。如果您使用该类,则存在调用不属于接口的方法(无意中)的风险。

答案 1 :(得分:16)

实际需要强制转换时只有一个原因:当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();
}

答案 2 :(得分:15)

如果对象明确地实现了接口(public void IStorable.StoreThis(...)),那么转换是实际到达接口成员的最简单方法。

答案 3 :(得分:13)

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

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实施

答案 4 :(得分:10)

没有更多的背景,很难分辨。如果变量doc被声明为实现接口的类型,则转换是多余的。

你正在阅读哪本书的版本?如果它是“编程C#3.0”我今晚看看我在家时。

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

  • 为什么要插入问题中的陈述? (答案:如果doc具有适当的编译时类型,则不必)
  • 为什么显式强制转换为已实现的接口或基类? (答案:显式接口实现,如另一个答案所示,也是为了在将转换值作为参数传递时选择一个不太具体的重载。)
  • 为什么要使用界面? (答案:使用界面类型意味着您以后不太容易改变具体类型。)

答案 5 :(得分:3)

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

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

答案 6 :(得分:2)

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

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

假设您需要一个按钮并将其放在屏幕上。您正在接收传入或来自其他功能的按钮,如下所示:

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

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

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

因为你只需要Component中的方法(两个按钮和Toggle的父节点,这可能是一个接口 - 几乎就我们的目的而言),如果你写了这样的第一行:

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

你不必重构任何东西 - 它本来就有效。

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

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

答案 7 :(得分:2)

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

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

这是一个简单的例子:

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类实例的对象。然后我可以使用界面找出他们可能拥有的其他内容。

有很多不同的原因可以让你采用这种方法在你的对象上使用接口,但它为你提供了一种宽松的方式来访问你的对象,而不必确切地知道它是什么。

想想世界上所有的信用卡,每个公司都有自己的信用卡,但接口是相同的,所以每个读卡器都可以按照标准刷卡。与接口的使用类似。

答案 8 :(得分:1)

正如已经指出的那样,铸件是多余的,不是必需的。然而,它是一种更明确的编码形式,对初学者来说有助于他们的理解。

在介绍性教科书中,最好明确地采取行动,而不是让compliler隐含地做事,这对初学者来说会更加困惑。

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

答案 9 :(得分:0)

关键是,对象(你从哪里得到它?)可能实现接口,在这种情况下抛出可以捕获和处理的异常。当然你可以使用“is”运算符来检查,并使用“as”运算符来代替C样式的强制转换。

答案 10 :(得分:0)

允许代码片段之间的最大分离......

有关更多信息,请参阅以下文章: Interfaces

答案 11 :(得分:0)

您要显式转换为接口的主要原因是,如果接口的成员是显式实现的(即使用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!