oop概念:在c#中,向接口传递对象引用和创建类对象有什么区别?
本文关键字:创建 对象引用 对象 区别 什么 概念 接口 oop | 更新日期: 2023-09-27 17:51:17
我有一个类CustomerNew
和一个接口ICustomer
:
public class CustomerNew : ICustomer
{
public void A()
{
MessageBox.Show("Class method");
}
void ICustomer.A()
{
MessageBox.Show("Interface method");
}
public void B()
{
MessageBox.Show("Class Method");
}
}
public interface ICustomer
{
void A();
}
我把这两行代码搞糊涂了。
ICustomer objnew = new CustomerNew();
CustomerNew objCustomerNew = new CustomerNew();
objnew.B(); // Why this is wrong?
objCustomerNew.B(); // This is correct because we are using object of class
第一行代码意味着我们在objnew
中传递CustomerNew类的对象引用,我正确吗?如果是,那么为什么我不能访问interface objnew
类的方法B() ?
有人能详细解释一下这两个吗?
接口有许多特性和用法,但最重要的一点是能够通过商定的契约向外界展示功能。
让我给你举个例子。考虑一下汽水自动售货机。它有一个或两个插槽供你输入硬币,几个按钮供你选择正确的苏打水类型,一个按钮供你分配苏打水(除非选择按钮也有这个功能)。
现在,这是一个接口。这将机器的复杂性隐藏在界面后面,并向您提供一些选择。但是,这里是重要的部分,内部机器有很多功能,它甚至可能有其他按钮和旋钮,通常在那里为维护人员测试或操作机器,当出现问题时,或者当他们必须清空它的钱或装满苏打水。
机器的这一部分是对你隐藏的,你只能访问外部接口的创建者添加到该接口的任何内容。
你甚至不知道这台机器在幕后是如何运作的。我可以创造一台新的自动贩卖机,它可以从附近的工厂传送苏打水,并将你添加的硬币直接传送到银行,你也不会知道。更不用说我会变得富有,但那是另一回事。
那么,回到你的代码。
您显式地将objnew
声明为ICustomer
。无论你把放在后面,这个接口都是隐藏的。你只能访问作为接口的一部分声明的。
另一个变量被声明为具有底层对象的类型,因此您可以完全访问其所有公共功能。你可以把它想象成打开自动售货机的锁,然后打开它的前门。
实际上接口也是一种类型(你不能创建接口的实例,因为它们只是元数据)
由于CustomerNew
实现了ICustomer
, CustomerNew
的一个实例可以被提升到到ICustomer
。当CustomerNew
类型为ICustomer
时,只能访问ICustomer
成员。
这是因为c#是一种强类型语言,因此,为了访问特定的成员(即方法,属性,事件…),您需要一个对象引用,该对象引用由定义您想要访问的成员的类型限定(即。您需要将CustomerNew
对象存储在CustomerNew
类型的引用中以访问方法B
)。
OP说:
因此,由于上转换,我们只能访问内部的那些方法界面,对吗?UPCASTING是背后的主要原因?
是的。一个简单的解释是,实现ICustomer
的对象不应该强制是CustomerNew
。要访问CustomerNew
成员,需要将下cast一个ICustomer
引用到CustomerNew
。
由于类和接口都是类型,而c#是一种强类型语言,因此可以通过提供对象的实际类型来访问对象成员。这就是为什么需要使用强制类型转换。
例如,您的代码执行隐式向上转换:
// This is an implicit cast that's equivalent to
// ICustomer objnew = (ICustomer)new CustomerNew()
ICustomer objnew = new CustomerNew();
隐式向上转换是可能的,因为编译器已经知道CustomerNew
实现了ICustomer
自省CustomerNew
元数据,而你不能隐式地-向下转换 ICustomer
引用,因为谁知道谁实现了ICustomer
?它可以是CustomerNew
或任何其他类,甚至是结构体:
ICustomer asInterface = new CustomerNew();
// ERROR: This won't compile, you need to provide an EXPLICIT DOWNCAST
CustomerNew asClass1 = asInterface;
// OK. You're telling the compiler you know that asInterface
// reference is guaranteed to be a CustomerNew too!
CustomerNew asClass2 = (CustomerNew)asInterface;
如果不确定ICustomer
是否是CustomerNew
,可以使用as
操作符,如果不可能强制转换,它不会在运行时抛出异常:
// If asInterface isn't also a CustomerNew, the expression will set null
CustomerNew asClass3 = asInterface as CustomerNew;
第一行:
ICustomer objnew
您指示编译器将objnew
视为ICustomer
,并且由于接口没有声明B()
方法,因此它会出错。
第二行:
CustomerNew objCustomerNew
您将objCustomerNew
称为CustomerNew
,由于它确实指定了B()
方法,因此它编译得很好。
这与编译时(即静态)类型检查有关。如果你看这两行
ICustomer objnew = new CustomerNew();
objnew.B();
您可以清楚地看到objnew
引用的对象有一个B()
方法,因此您知道第二行在运行时不会有问题。
但是,编译器不是这样看它的。编译器使用一组相当简单的规则来确定是否报告错误。当它查看调用objnew.B()
时,它使用objnew
的静态(即声明)类型来确定接收者的类型。静态(声明的)类型是ICustomer
,并且该类型没有声明B()
方法,因此报告错误。
那么为什么编译器忽略给引用的初始值呢?以下是两个原因:
First:因为它通常不能使用这些信息,因为——通常——可能会有条件赋值介入。例如,您可能有如下的代码
ICustomer objnew = new CustomerNew();
if( some complicated expression ) objnew = new CustomerOld() ;
objnew.B();
其中CustomerOld
是实现接口但没有B()
方法的某个类。
第二:可能没有初始化表达式。这种情况尤其发生在参数方面。当编译器编译一个方法时,它无法访问对该方法的所有调用的集合。考虑
void f( ICustomer objnew ) {
objnew.B();
}
为了保持规则的简单性,参数和(其他)变量使用相同的规则。
你当然可以想象一种规则不同的语言,但这就是c#、Java和类似语言的工作原理。
您的objnew
变量是对实现ICustomer
的对象的引用。请注意,这可以是任何对象,只要它实现了ICustomer
。因此,这个引用所公开的所有内容都是iccustomer的成员,在您的示例中只有方法A()
。
如果你想访问objnew
引用的对象的B()
方法,你必须显式地将其转换为CustomerNew
引用(这是c#的类型安全),例如:
CustomerNew objCustomerNew = objnew as CustomerNew;
objCustomerNew.B();
或
(objnew as CustomerNew).B();
请记住,objnew
可以是实现ICustomer
的任何类型,因此,如果稍后实现其他ICustomer
类,则转换objnew as CustomerNew
可以解析为null
。
简单来说,我想说objnew是一个类型为 iccustomer 的变量,它包含一个类型为CustomerNew的引用。由于 iccustomer 没有方法B()的声明或定义(因为它是接口),编译器在编译时检查该方法并抛出编译类型错误。
现在来解释一下,在编译时,编译器检查变量的Type的MethodDef表,即 iccustomer ,并沿着类定义的层次结构往上走。如果它在定义表中找到方法,它将引用已找到的方法(这再次取决于其他场景,如覆盖,我们可以在其他主题中讨论)。我们可以用一个简单的例子来理解这一点,我想在这里讨论这个例子
public interface IMyInterface
{
}
public class MyBaseClass:IMyInterface
{
public string B()
{
return "In Abstract";
}
}
public class MyDerivedClass : MyBaseClass
{
}
请仔细查看上面代码片段中的类层次结构。现在进入实现部分,如下所示,
MyBaseClass inst = new MyDerivedClass();
inst.B(); //Works fine
MyDerivedClass inst1 = new MyDerivedClass();
inst1.B(); //Works fine
IMyInterface inst2 = new MyDerivedClass();
inst2.B(); //Compile time error
场景1工作得很好,原因很明显,正如我之前解释的那样,inst是类型为MyBaseClass的变量,它包含类型为MyDerivedClass的引用,MyBaseClass有方法B()的定义。
现在来到场景2,它也工作得很好,但如何?inst1是一个类型为MyDerivedClass的变量,它包含了一个类型为MyDerivedClass的变量,但是方法B()在这个类型中没有定义,但是编译器所做的是在编译时检查MyDerivedClass的MethodDef表,并发现MyBaseClass如果在运行时引用的方法B()有定义。
所以现在我希望你明白我的观点,这解释了为什么场景3不工作