在Jesse Liberty的Programming C#(第142页)中,他提供了一个将对象转换为接口的示例。
interface IStorable
{
...
}
public class Document : IStorable
{
...
}
...
IStorable isDoc = (IStorable) doc;
...
这有什么意义,特别是如果对象的类仍然实现了接口?
EDIT1:澄清一下,我对推理的原因(如果有的话),不实施接口的原因感兴趣。此外,这本书是他的2001年第一版(基于C#1,所以这个例子可能与以后版本的C#没有密切关系。)
EDIT2:我在代码中添加了一些上下文
答案 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!