OOPS 概念:在 C# 中将对象引用传递给接口和创建 class 对象有什么区别?

OOPS Concepts: What is the difference in passing object reference to interface and creating class object in C#?

我有一个 class、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 class 的对象引用,我说得对吗?如果是,那为什么我不能使用 interface objnew 访问 class 的方法 B()?

谁能详细解释一下这两个。

第一行:

ICustomer objnew

您指示编译器将 objnew 视为 ICustomer,并且由于接口未声明 [​​=14=] 方法,因此会出错。

第二行:

CustomerNew objCustomerNew

您将 objCustomerNew 称为 CustomerNew,因为它确实指定了一个 B() 方法,所以它编译得很好。

接口有许多特性和用途,但一个核心问题是能够在约定的合同中向外界展示功能。

让我举个例子。考虑一台汽水自动售货机。它有一两个插槽供您输入硬币,几个按钮供您选择正确类型的苏打水和一个分配苏打水的按钮(除非选择按钮也可以)。

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

但是,重要的是,内部机器有很多功能,甚至可能还有其他按钮和旋钮,通常供维护人员测试或者在出现问题时操作机器,或者当他们不得不清空机器以换取钱或装满苏打水时。

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

你甚至不知道这台机器在幕后究竟是如何运作的。我可以创建一个新的自动售货机,从附近的工厂传送苏打水并将您直接添加的硬币传送到银行,您会 none 更聪明。不说我会发财,那是另外一回事了。

所以,回到你的代码。

您明确声明 objnewICustomer。无论你把 放在 后面,这个界面都是隐藏的。您只能访问声明为 作为该接口的一部分 .

的任何内容

另一个变量被声明为具有底层对象的类型,因此您可以完全访问它的所有 public 功能。把它想象成解锁自动售货机并在正面打开的情况下使用它。

实际上接口也是一种类型(您不能创建接口的实例,因为它们只是元数据)。

由于 CustomerNew 实现了 ICustomerCustomerNew 的实例可以 向上转换 ICustomer。当 CustomerNew 键入 ICustomer 时,您只能访问 ICustomer 成员。

这是因为 C# 是一种强类型语言,因此,为了访问特定的成员(即方法、属性、事件...),您需要一个对象引用来限定类型,该类型定义了您要访问的成员 (即您需要将 CustomerNew 对象存储在类型 CustomerNew 的引用中以访问方法 B.

更新

OP 说:

So due to upcasting we can only access those methods which are inside interface, correct ? UPCASTING is the main reason behind this ?

是的。一个简单的解释是实现 ICustomer 的对象不应强制为 CustomerNew。您需要 向下转换CustomerNewICustomer 引用才能访问 CustomerNew 成员。

由于 classes 和接口都是类型,并且 C# 是一种强类型语言,因此您可以通过提供其实际类型来访问对象成员。这就是您需要使用 casts 的原因。

例如,您的代码执行隐式向上转换:

// This is an implicit cast that's equivalent to
// ICustomer objnew = (ICustomer)new CustomerNew()
ICustomer objnew = new CustomerNew();

隐式向上转换是可能的,因为编译器已经知道 CustomerNew 实现 ICustomer 内省 CustomerNew 元数据,而你不能隐式地-向下转换 一个 ICustomer 参考,因为谁知道谁实施了 ICustomer?它可以是 CustomerNew 或任何其他 class 甚至是结构:

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;

如果您不确定 ICustomerCustomerNew,您可以使用 as 运算符,它不会在 运行 期间抛出异常-时间如果演员是不可能的:

// If asInterface isn't also a CustomerNew, the expression will set null
CustomerNew asClass3 = asInterface as CustomerNew;

您的 objnew 变量是对实现 ICustomer 的对象的引用。请注意,这可以是 任何 对象,只要它实现了 ICustomer。因此,此引用公开的所有内容都是 ICustomer 的成员,这只是您示例中的方法 A()

如果您确实想访问 objnew 引用的对象的 B() 方法,您必须将其显式转换为 CustomerNew 引用(这是 C# 的类型工作安全),例如那:

CustomerNew objCustomerNew = objnew as CustomerNew;
objCustomerNew.B();

(objnew as CustomerNew).B();

请记住,objnew 可以是实现 ICustomer 的任何类型,因此如果您实现其他 ICustomer,转换 objnew as CustomerNew 可以解析为 null ] 类稍后。

这与编译时(即静态)类型检查有关。如果你看这两行

ICustomer objnew = new CustomerNew();
objnew.B(); 

你可以清楚地看到objnew所指的对象有一个B()方法,所以你知道第二行在运行时是没有问题的。

但是,编译器并不是这样看的。编译器使用一组相当简单的规则来确定是否报告错误。当它看起来是调用 objnew.B() 时,它使用 objnew 的静态(即声明的)类型来确定接收者的类型。静态(已声明)类型为 ICustomer,该类型未声明 [​​=14=] 方法,因此报告错误。

那么为什么编译器会忽略给引用的初始值呢?这里有两个原因:

首先:因为它通常不能使用该信息,因为——一般来说——可能会有条件分配介入。例如,您的代码可能如下所示

ICustomer objnew = new CustomerNew();
if( some complicated expression ) objnew = new CustomerOld() ;
objnew.B(); 

其中 CustomerOld 是一些 class 实现接口但没有 B() 方法。

其二:可能没有初始化表达式。这种情况尤其适用于参数。当编译器编译一个方法时,它无权访问对该方法的所有调用的集合。考虑

void f( ICustomer objnew ) {
    objnew.B(); 
}

为了保持规则简单,参数和(其他)变量使用相同的规则。

您当然可以想象一种规则不同的语言,但这就是 C#、Java 和类似语言的工作方式。

简单来说,我会说 objnew 是一个 ICustomer 类型的变量,它包含 类型的引用新客户。由于 ICustomer 没有方法 B() 的声明或定义(它不能有,因为它是一个接口)编译器在编译时检查并抛出编译类型错误。

现在开始解释,在编译时编译器检查变量类型的 MethodDef table 即 ICustomer 并向上走 class定义。如果它在定义 table 中找到该方法,它将引用已找到的方法(这又取决于其他场景,例如我们可以在其他主题中讨论的覆盖)。我们可以使用我想在这里讨论的一个简单示例来理解这一点

public interface IMyInterface
{

}

public class MyBaseClass:IMyInterface
{
    public string B()
    {
        return "In Abstract";
    }
}

public class MyDerivedClass : MyBaseClass
{

}

请浏览上述代码段中的 class 层次结构。下面进入实现部分,

        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 工作正常,原因很明显,正如我之前解释的那样,instMyBaseClass 类型的变量,其中包含一个MyDerivedClassMyBaseClass 类型的引用具有方法 B() 的定义。

现在进入场景 2,它也可以正常工作,但是如何呢? inst1 是一个 MyDerivedClass 类型的变量,它又包含一个 MyDerivedClass 类型的变量,但是方法 B()未在此类型中定义,但编译器所做的是在编译时检查 MyDerivedClass 的 MethodDef table 并发现 MyBaseClass如果它在 运行 时间引用的方法 B() 具有定义。

所以现在我希望你明白我的意思,这解释了为什么方案 3 不起作用。