.NET的基元类型包括什么及Unmanaged和Blittable类型详解

 更新时间:2023年06月12日 10:28:26   作者:Artech  
这篇文章主要介绍了.NET的基元类型包括什么及Unmanaged和Blittable类型详解,Unmanaged类型可以理解不涉及托管对象引用的值类型,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下

在讨论.NET的类型系统的时候,我们经常提到“基元类型(Primitive Type)”的概念,我发现很多人并没有真正理解基元类型就究竟包含哪些(比如很多人觉得字符串是基元类型)。除了明确界定基元类型外,本篇文章还会简单介绍额外两种关于类型的概念——Unmanaged类型和Blittable类型。

一、Primitive Type
二、Unmanaged Type
三、Blittable Type

一、Primitive Type

.NET下的基元类型(Primitive Type)如下14个。我们可以这样来记:长度(字节数)分别为1、2、4、8的有/无符号的整数;外加两个基于指针宽度(下x86=4; x64=8)的整数,计10个。长度(字节数)分别为4和8的单精度和双精度浮点数,计2个。外加布尔类型和字符类型, 计2个。所以我们熟悉的String(string)和Decimal(decimal)并不是基元类型。

  • 整数(10):Byte(byte)/SByte(sbyte), Int16(short)/UInt16(ushort), Int32(int)/UInt32(uint), Int64(long)/UInt64(ulong), IntPtr(nint)/UIntPtr(nuint)
  • 浮点(2):Float(float), Double(double)
  • 布尔(1):Boolean(bool)
  • 字符(1):Char(char)

对于某个指定的Type对象,我们可以利用它的IsPrimitive属性确定它是否为基元类型。

public abstract class Type
{
    public bool IsPrimitive { get; }
}

Type对象的IsPrimitive属性值最终来源于RuntimeTypeHandle类型如下这个内部静态方法IsPrimitive。从该方法的实现和CorElementType的枚举成员也可以看出,枚举值2-13,外加CorElementType.I(IntPtr)和CorElementType.U(UIntPtr)这14个类型属于基元类型的范畴,这与上面的列表是一致的。

public struct RuntimeTypeHandle
{
    [SecuritySafeCritical]
    internal static bool IsPrimitive(RuntimeType type)
    {
        CorElementType corElementType = GetCorElementType(type);
        if (((int)corElementType < 2 || (int)corElementType > 13) && corElementType != CorElementType.I)
        {
            return corElementType == CorElementType.U;
        }
        return true;
    }
}
[Serializable]
internal enum CorElementType : byte
{
    End = 0,
    Void = 1,
    Boolean = 2,
    Char = 3,
    I1 = 4,
    U1 = 5,
    I2 = 6,
    U2 = 7,
    I4 = 8,
    U4 = 9,
    I8 = 10,
    U8 = 11,
    R4 = 12,
    R8 = 13,
    String = 14,
    Ptr = 15,
    ByRef = 16,
    ValueType = 17,
    Class = 18,
    Var = 19,
    Array = 20,
    GenericInst = 21,
    TypedByRef = 22,
    I = 24,
    U = 25,
    FnPtr = 27,
    Object = 28,
    SzArray = 29,
    MVar = 30,
    CModReqd = 31,
    CModOpt = 32,
    Internal = 33,
    Max = 34,
    Modifier = 64,
    Sentinel = 65,
    Pinned = 69
}

二、Unmanaged Type

顾名思义,Unmanaged类型可以理解不涉及托管对象引用的值类型。如下的类型属于Unmanaged 类型的范畴:

  • 14种基元类型+Decimal(decimal)

  • 枚举类型

  • 指针类型(比如int*, long*)

  • 只包含Unmanaged类型字段的结构体

如果要求泛型类型是一个Unmananged类型,我们可以按照如下的方式使用unmanaged泛型约束。我在《如何计算一个实例占用多少内存?》提到过,只有Unmananged类型采用使用sizeof操作符计算大小。

public static unsafe int SizeOf<T>() where T : unmanaged
{
    return sizeof(T);
}

三、Blittable Type

Blittable是站在基于P/Invoke的互操作(InterOp)角度对传递的值是否需要进行转换(Marshaling)而作的分类。Blittable类型要求在托管内存和非托管内存具有完全一致的表示。如果某个参数为Blittable类型,在一个P/Invoke方法调用非托管方法的时候,该参数就无需要作任何的转换。与之类似,如果调用方法的返回值是Blittable类型,在回到托管世界后也无需转换。如下的类型属于Blittable类型范畴:

  • 除Boolean(bool)和Char(char)之外的12种基元类型,因为布尔值True在不同的平台可能会表示成1或者-1,对应的字节数可能是1、2或者4,字符涉及不同的编码(Unicode和ANSI),所以这两种类型并非Blittable类型;
  • Blittable基元类型的一维数组;
  • 采用Sequential和Explicitly布局的且只包含Blittable类型成员的结构或者类,因为采用这两种布局的对象最终会按照一种确定的格式转换成对应的C风格的结构体。如果采用Auto布局,CLR会按照少占用内存的原则对字段成员重新排序,意味着其内存结构是不确定的。

顺便强调一下,DateTime/DateTimeOffset都采用Auto布局(如下所示),Guid虽然是一个默认采用Sequential布局的结构体,但是最终映射在内存种的字节依赖于字节序(Endianness),所以具有这三种类型字段的结构体或者类都不是Blittable类型。

[Serializable]
[StructLayout(LayoutKind.Auto)]
public struct DateTime
{ }
[Serializable]
[StructLayout(LayoutKind.Auto)]
public struct DateTimeOffset
{ }

只有Blittable类型的实例才能调用GCHandle的静态方法Alloc为其创建一个Pinned类型的GC句柄。以如下的代码为例,类Foobar的两个属性都是Blittable类型,我们通过标注在类型上的StructLayoutAttribute将布局类型显式设置成Sequential使其称为了一个Blittable类型。

GCHandle.Alloc(new Foobar(), GCHandleType.Pinned);
[StructLayout(LayoutKind.Sequential)]
public class Foobar
{
    public int Foo { get; set; }
    public double Bar { get; set; }
}

如果Foobar类定义成如下的形式,都不能使其称为一个Blittable类型。前者默认采用Auto布局,后者的Bar属性并不是Blittable类型。如果将这样Foobar对象作为参数按照上面的方式调用GCHandle. Alloc方法,会直接抛出ArgumentException异常,并提示“Object contains non-primitive or non-blittable data. (Parameter 'value')”。

public class Foobar
{
    public int Foo { get; set; }
    public double Bar { get; set; }
}
[StructLayout(LayoutKind.Sequential)]
public class Foobar
{
    public int Foo { get; set; }
    public DateTime Bar { get; set; }
}

到此这篇关于.NET的基元类型包括什么及Unmanaged和Blittable类型详解的文章就介绍到这了,更多相关.NET的基元类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • asp.net 在客户端显示服务器端任务处理进度条的探讨

    asp.net 在客户端显示服务器端任务处理进度条的探讨

    由于 HTTP 协议本身的无状态性,B/S结构的程序无法像C/S程序那样,实时显示程序处理的进度。搜索一下网上,一般都是采用静态变量保存程序执行进度的方法实现,但是,这种方法是完全错误的,在并发的情况下,多个用户访问一个程序,会造成混乱。
    2009-09-09
  • ASP.NET(C#)中操作SQLite数据库实例

    ASP.NET(C#)中操作SQLite数据库实例

    最近项目中有使用到SQLite数据库,于是查找资料,编写了一个ASP.NET基于C#语言的SQLite数据库操作实例.大家看代码就可以看懂了,和以往使用ADO.NET操作SQL数据库类似.
    2009-12-12
  • ASP.NET中TimeSpan的用法实例解析

    ASP.NET中TimeSpan的用法实例解析

    这篇文章主要介绍了ASP.NET中TimeSpan的用法,以实例的形式具体分析了TimeSpan应用中的各种常见常量、字段、属性与方法等,非常具有参考学习价值,需要的朋友可以参考下
    2014-10-10
  • 使用 ServiceStack.Text 序列化 json的实现代码

    使用 ServiceStack.Text 序列化 json的实现代码

    今天发篇文章总结下自己使用 ServiceStack.Text 来序列化 json。它的速度比 Newtonsoft.Json 快很多,在测试时发现比 fastJson 还快些
    2013-06-06
  • .net framework中引进的var对象类型声明

    .net framework中引进的var对象类型声明

    安装了vs 2008之后,在后台代码中,resharper插件对后台所有局部变量进行提示建议,显示需要采用use implicitly typed local variable declaration,通过代码修改建议
    2010-12-12
  • ASP.Net巧用窗体母版页实例

    ASP.Net巧用窗体母版页实例

    这篇文章主要介绍了ASP.Net巧用窗体母版页的方法,以实例形式详细分析了母版页的用途及嵌套用法,具有一定的学习借鉴价值,需要的朋友可以参考下
    2014-11-11
  • .NET高级调试之sos命令输出看不懂的处理方法

    .NET高级调试之sos命令输出看不懂的处理方法

    .NET高级调试属于一个偏冷门的领域,国内可观测的资料比较少,所以很多东西需要你自己去探究源代码,然后用各种调试工具去验证,下面通过本文给大家分享.NET高级调试之sos命令输出的相关知识,感兴趣的朋友一起看看吧
    2024-03-03
  • 解析WPF实现音频文件循环顺序播放的解决方法

    解析WPF实现音频文件循环顺序播放的解决方法

    本篇文章是对WPF实现音频文件循环顺序播放的方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • Visual Studio 2013更新内容简介

    Visual Studio 2013更新内容简介

    这篇文章主要为大家分享了Visual Studio 2013更新内容简介,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • 详解最好的.NET开源免费ZIP库DotNetZip(.NET组件介绍之三)

    详解最好的.NET开源免费ZIP库DotNetZip(.NET组件介绍之三)

    本篇文章主要介绍了.NET开源免费ZIP库DotNetZip组件的介绍,可以实现对文件的压缩和解压,有兴趣的朋友可以了解一下。
    2016-12-12

最新评论