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() ?

有人能详细解释一下这两个吗?

oop概念:在c#中,向接口传递对象引用和创建类对象有什么区别?

接口有许多特性和用法,但最重要的一点是能够通过商定的契约向外界展示功能。

让我给你举个例子。考虑一下汽水自动售货机。它有一个或两个插槽供你输入硬币,几个按钮供你选择正确的苏打水类型,一个按钮供你分配苏打水(除非选择按钮也有这个功能)。

现在,这是一个接口。这将机器的复杂性隐藏在界面后面,并向您提供一些选择。

但是,这里是重要的部分,内部机器有很多功能,它甚至可能有其他按钮和旋钮,通常在那里为维护人员测试或操作机器,当出现问题时,或者当他们必须清空它的钱或装满苏打水。

机器的这一部分是对你隐藏的,你只能访问外部接口的创建者添加到该接口的任何内容。

你甚至不知道这台机器在幕后是如何运作的。我可以创造一台新的自动贩卖机,它可以从附近的工厂传送苏打水,并将你添加的硬币直接传送到银行,你也不会知道。更不用说我会变得富有,但那是另一回事。

那么,回到你的代码。

您显式地将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不工作