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 更聪明。不说我会发财,那是另外一回事了。
所以,回到你的代码。
您明确声明 objnew
为 ICustomer
。无论你把 放在 后面,这个界面都是隐藏的。您只能访问声明为 作为该接口的一部分 .
的任何内容
另一个变量被声明为具有底层对象的类型,因此您可以完全访问它的所有 public 功能。把它想象成解锁自动售货机并在正面打开的情况下使用它。
实际上接口也是一种类型(您不能创建接口的实例,因为它们只是元数据)。
由于 CustomerNew
实现了 ICustomer
,CustomerNew
的实例可以 向上转换 到 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
。您需要 向下转换 对 CustomerNew
的 ICustomer
引用才能访问 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;
如果您不确定 ICustomer
是 CustomerNew
,您可以使用 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 工作正常,原因很明显,正如我之前解释的那样,inst 是 MyBaseClass 类型的变量,其中包含一个MyDerivedClass 和 MyBaseClass 类型的引用具有方法 B() 的定义。
现在进入场景 2,它也可以正常工作,但是如何呢? inst1 是一个 MyDerivedClass 类型的变量,它又包含一个 MyDerivedClass 类型的变量,但是方法 B()未在此类型中定义,但编译器所做的是在编译时检查 MyDerivedClass 的 MethodDef table 并发现 MyBaseClass如果它在 运行 时间引用的方法 B() 具有定义。
所以现在我希望你明白我的意思,这解释了为什么方案 3 不起作用。
我有一个 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 更聪明。不说我会发财,那是另外一回事了。
所以,回到你的代码。
您明确声明 objnew
为 ICustomer
。无论你把 放在 后面,这个界面都是隐藏的。您只能访问声明为 作为该接口的一部分 .
另一个变量被声明为具有底层对象的类型,因此您可以完全访问它的所有 public 功能。把它想象成解锁自动售货机并在正面打开的情况下使用它。
实际上接口也是一种类型(您不能创建接口的实例,因为它们只是元数据)。
由于 CustomerNew
实现了 ICustomer
,CustomerNew
的实例可以 向上转换 到 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
。您需要 向下转换 对 CustomerNew
的 ICustomer
引用才能访问 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;
如果您不确定 ICustomer
是 CustomerNew
,您可以使用 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 工作正常,原因很明显,正如我之前解释的那样,inst 是 MyBaseClass 类型的变量,其中包含一个MyDerivedClass 和 MyBaseClass 类型的引用具有方法 B() 的定义。
现在进入场景 2,它也可以正常工作,但是如何呢? inst1 是一个 MyDerivedClass 类型的变量,它又包含一个 MyDerivedClass 类型的变量,但是方法 B()未在此类型中定义,但编译器所做的是在编译时检查 MyDerivedClass 的 MethodDef table 并发现 MyBaseClass如果它在 运行 时间引用的方法 B() 具有定义。
所以现在我希望你明白我的意思,这解释了为什么方案 3 不起作用。