在 C# 中引用类型的内存方面的显式转换解释

Posted

技术标签:

【中文标题】在 C# 中引用类型的内存方面的显式转换解释【英文标题】:Explicit cast explanation in terms of memory for reference type in C# 【发布时间】:2016-10-28 04:57:45 【问题描述】:

在 MSDN 中,“对于引用类型,如果您需要从基本类型转换为派生类型,则需要显式转换”。

在 wiki 中,“在编程语言理论中,引用类型是指引用内存中的对象的数据类型。另一方面,指针类型指的是内存地址。引用类型可以被认为是隐式取消引用的指针。”在 C 中就是这种情况。

在 C# 中考虑显式转换引用类型时如何解释内存存储过程?

【问题讨论】:

对象存储在内存中,独立于对对象的引用。转换用于获取将对象视为特定类型的引用。 From the same MSDN page: "引用类型之间的强制转换操作不会改变底层对象的运行时类型;它只会改变用作对该对象的引用的值的类型。" 问题讨论C#,所以标签CC++应该被删除 【参考方案1】:

在大多数情况下,引用变量和指针变量之间并没有太多可以想象的区别。两者都指向内存中的一个位置。引用(或指针)变量的类型告诉编译器可以使用它执行哪些操作。

首先考虑 C++ 对象指针,而不是(主要)与基本类型(例如 int 或 byte)一起使用的 C 指针。它真的和 C# 中的几乎一样:

MyBaseClass* a = new MyBaseclass();
a->BaseMethod(); // Call method using -> operator (dereference and call)

MyBaseClass* b = new MyDerivedClass();
b->DerivedMethod(); // Error: MyBaseClass has no such method

// Proper C++-Style casting. 
MyDerivedClass* c = dynamic_cast<MyDerivedClass*>(b);
// Shortcut to the above, does not do the type test. 
// MyDerivedClass* c = (MyDerivedClass*)b; 
c->DerivedMethod(); // Ok

这几乎 1:1 转换为 C#,因此引用类型(从程序员的角度来看)只是具有已定义类型的指针。唯一明显的区别是 C# 中的直接 C-Style 强制转换等同于 C++ 中的 try_cast,这将确保您永远不会将错误的目标实例分配给引用变量。

所以引用类型和指向对象的指针之间的区别是(其中大部分都隐含在 C# 是一种托管语言这一事实​​中):

引用变量永远不能指向无效内存(NULL 除外)。 引用变量永远不能指向不属于其类型的对象。 为引用变量赋值时,始终会测试类型。 对引用变量的强制转换需要检查目标对象是否属于给定类型。

【讨论】:

【参考方案2】:

引用对象存储在堆中,可以从代码中引用它们。对象,因为它在堆上,是给定类型的。

从代码中,您可以创建对它的引用,这些引用可以转换为其他一些类型。

现在,有几个案例,在参考文章中进行了描述。我将使用那里的示例使其更容易。

1.隐式转换

当您没有在代码中特别要求它时,就会发生隐式转换。编译器必须自己知道如何做到这一点。

1.1.值类型

如果您尝试转换的值的类型是大小,这允许您将其存储在内存大小中,该大小使您想要转换为的类型的大小,然后编译器将允许您这样做。这主要用于数值,因此请遵循您参考文章中的示例:

// Implicit conversion. num long can
// hold any value an int can hold, and more!
int num = 2147483647;
long bigNum = num;

所以由于 int 比 long '小',编译器会让你这样做。

1.2.引用类型

假设您有以下类定义:

class Base     



class Derived : Base 
    public int IntProperty  get; set; 
    public int CalculateSomething ()
    
         return IntProperty * 23;
    

然后您可以安全地进行如下转换:

Derived d = new Derived();
Base b = d;

这是因为您在堆上创建的对象 d 的类型为 Derived,并且由于它是类型 Base 的派生类型,因此可以保证拥有 Base 拥有的所有成员。所以转换引用并使用Derived 对象作为Base 对象是安全的。因为Derived IS Base (Derived : Base)。

2。显式转化

假设我们的项目中有另一个类:

class DerivedLike

    public int IntProp  get; set; 
    public int CalculateSomethingElse()
    
        return IntProp * 23;
    

如果我们写

DerivedLike dl = new DerivedLike();
Derived d = dl;

我们将从我们的编译器中得知,它不能将类型 DerivedLike 隐式转换为 Derived

这是因为这两种引用类型完全不同,所以编译器不允许你这样做。这些类型具有不同的属性和方法。

2.1.实现显式转换

只要你不能自己从派生类转换到基类,在大多数其他情况下你可以编写一个运算符。

如果要继续从 DerivedLike 到 Derived 的转换,我们必须在 DerivedLike 类中实现一个转换运算符。它是一个静态运算符,它告诉如何将一种类型转换为另一种类型。转换运算符可以是隐式的,也可以是显式的。显式将要求开发人员通过在括号中提供类型名称来显式转换它。

在隐式和显式运算符之间进行选择的建议是,如果转换可能引发异常,则应该是显式的,以便开发人员有意识地进行转换。

让我们更改代码以满足该要求:

class DerivedLike

    public static explicit operator Derived(DerivedLike a)
    
        return new Derived()  IntProperty = a.IntProp;
    
    public int IntProp  get; set; 
    public int CalculateSomethingElse()
    
        return IntProp * 23;
    

所以现在可以正常编译了:

DerivedLike dl = new DerivedLike();
Derived d = (Derived)dl;

回到内存主题,请注意,通过这种转换,您现在将在堆上拥有两个对象。

这里创建的一个:

DerivedLike dl = new DerivedLike();

在这里创建第二个:

Derived d = (Derived)dl;

堆上的对象不能改变它的类型。

希望这可以澄清。

【讨论】:

以上是关于在 C# 中引用类型的内存方面的显式转换解释的主要内容,如果未能解决你的问题,请参考以下文章

Scala 中的显式类型转换

C#面向对象整理

C# 泛型是引用类型还是值类型,是根据啥判断?

C# “值类型“和“引用类型“在内存的分配

C# “值类型“和“引用类型“在内存的分配

显式实例化模板类的显式实例化模板方法