重庆熊猫 Loading

C#教程 - C#数据类型(Data Type)

更新记录
转载请注明出处。
2022年9月10日 发布。
2022年9月10日 从笔记迁移到博客。

数据类型(Data Type)概念

简单说就是:值的蓝图,某种值数据的特征。约等于数据的物理结构 + 数据的逻辑结构 + 数据操作行为。数据的物理结构决定了如何将值存储到计算机的内存中,数据的逻辑结构决定了如何使用该值,数据操作行为决定了值可以进行的操作。

为什么分成不同的数据类型

更高效的进行内存分配、数据存储、数据处理

预定义类型(Predefined Type)(built-in types)

说明

所有 预定义类型 都直接隐射到底层的 .NET Runtime类型。C#提供16种预定义类型,13种简单类型,3种非简单类型。
注意:预定义类型关键字全是小写

image

image

预定义16种类型分别是:

  • 11种数值类型
    • 整数数值:sbyte/byte/short/ushort/int/uint/long/ulong。
    • 浮点数数值:2种浮点数类型(float、double)。
    • 高精度数值:1种高精度小数类型(decimal),常用于货币。
      注意:在C#中数值不具有布尔意义。
  • 1种boolean类型
  • 1种Unicode编码的char类型
  • 3种非简单类型
  • string 表示一个Unicode字符数组
  • object 其他类型的基类
  • dynamic 使用动态语言编写的程序集使用

预定义类型详细信息

image

image

数据类型分类(大类)

Value types(值类型)
Reference types(引用类型)
Generic type parameters(泛型类型参数)
Pointer types(指针类型)

在.NET中的预定义类型继承结构图

image

数值类型细节(整型、浮点型)

数值类型细节注意

float的有效精度7位
double的有效精度15位
decimal的有效精度28位
数值类型全部派生自System.ValueType类型
不要使用浮点数(float、double)进行需要高精度的运算

最佳实践

int和long是首选整型的首选类型,用于开发效率不敏感的程序
byte和short多用于效率优先和底层互操作

8位和16位整型运算注意

8位和16位整型不支持+, -, *, /, %运算,但可以转为其他整数类型进行处理
实例:

short s1 = 333;
short s2 = 333;
short s3 = s1 + s2; //error
short s4 = (short)(s1 + s2); //666

浮点数特殊的常量

float.NaN
float.PositiveInfinity
float.NegativeInfinity
double.NaN
double.PositiveInfinity
double.NegativeInfinity
double.MaxValue
double.MinValue
double.Epsilon
double.MaxValue
double.MinValue
double.Epsilon

注意:NaN和任何数都不相等,包括NaN自身也不相等

double与decimal对比

decimal运行效率大约比double慢10倍

decimal和double都无法非常精确的表示无限循环小数
image

布尔类型细节注意

布尔类型占用空间注意

虽然布尔类型只需要1bit内存空间,但实际上使用了1个byte(8bit)
因为字节是运行时和处理器最有效率处理数据的最小单位
如果非要使用1位,可以使用System.Collections.BitArray
比较运算符的结果都是布尔类型
注意:如果类型重载了运算符,结果也可以不是布尔类型
BitArray实例:

System.Collections.BitArray bitArray = new System.Collections.BitArray(5);
bitArray.Set(0, true);
Console.WriteLine(bitArray.Get(0));

布尔类型转换注意

布尔类型无法与数值类型进行相互转换

字符类型注意

字符串可以隐式的转为unsigned short类型以及比这个更大的数值类型,其他数值类型转换需要显式转换

char a = 'a';
int b = a;
ushort c = a;
short d = (short)a;  //需要显式转换
long e = a;

字符串类型注意

字符串是引用类型但其=赋值是overwrite的,是值复制
而且比较==是值比较

string panda1 = "Panda";
string panda2 = "Panda";
Console.WriteLine(panda2 == panda1); //true

字面量

整型字面量

236         //整型
236L        //长整型
236U        //无符号整数
236UL       //无符号长整型

image

字面量数值分隔符(Digital Separator)

注意:从C# 7版本开始支持
可以使用下划线作为分隔符,便于观看

int million = 1_000_000;

实例:

System.Console.WriteLine(9_814_072_356);

整型二进制字面量

注意:版本支持从C# 7 and above

int panda = 0b010100101;
int panda2 = 0b0101_0101_0101;  //使用_进行分割,16进制也可以使用

浮点数字面量

float  pandaFloat = 236F;
double pandaDouble1 = 666.666D;
double pandaDouble2 = .666D;
double pandaDouble3 = 666.66e-10;
decimal pandaDecimal = 666.666M;

image

字符字面量

char c1 = 'd';     //单个字符串
char c2 = '\n';     //简单转义序列
char c3 = '\x0061';   //十六进制转义序列
char c4 = '\u005a';   //Unicode转义序列

字符串字面量

string name = "Panda";
string code = "666";

逐字字面量:

​ 在字符串前加@

​ 字符串内的转义字符不会被转义

string name = @"Panda \t Panda";

注意:编译器会让相同的字符串字面量指向同一个堆地址

如果需要在逐字字面量中包含双引号,可以使用双双引号

string website = @"panda is mean ""Panda666.com"" ";
Console.WriteLine(website);
//panda is mean "Panda666.com"

插值字符串(interpolated string):

​ 在字符串前加$

​ 字符串内可以直接结合表达式和值

string website = "Panda666.com";
Console.WriteLine($"Website:{website}");

还可以设置值的显示format

string website = "Panda666.com";
int code = 666;
Console.WriteLine($"Website:{website} Code {code:F}");

插值字符串 和 逐字字面量 结合使用:

注意:$必须在@前面

string website = "Panda666.com";
int code = 666;
Console.WriteLine($@"\Website:""{website}"" Code {code:F}");

如何选择类型

对于整数类型,优先考虑使用int和long

其他整数类型通常用于互操作性或空间效率要求高

对于财务和金融运算,优先使用decimal

如果精度损失可以接受,可以使用double代替decimal

decimal大约比double慢10倍

用户定义类型

说明:5种类型可以由用户自定义

分别是:

​ 接口类型(Interface)

​ 类类型(Class)

​ 结构类型(Struct)

​ 枚举类型(Enum)

​ 委托类型(Delegate)

类型与存储结构

栈(Stack)

栈(Stack)是(LIFO,Last-In-First-Out,后进先出)的数据结构,栈存储以下类型:

​ 传递给方法形参的值

​ 局部变量的值

​ 程序当前的执行环境信息

image

堆(Heap)

堆(Heap)是一块内存区域,一般在堆区分配大块的内存用于存储对象

一般情况下,C#中不可以显式删除堆中数据,由CLR的GC自动进行删除回收内存
image

GC自动回收示意图
image

数据类型分类(按内存存储方式)

值类型(VALUE TYPES) 和 引用类型(REFERENCE TYPES)

image

值类型:

只需要一段存储空间,直接用于存储实际的数据

值类型的数据存放在栈中

值类型派生自System.ValueType

System.ValueType重写了Object类型中的所有虚成员(Virtual Members)

引用类型:

需要两段内存空间,一段内存用于存储引用,一段内存用于实际数据

引用指向实际数据地址

引用类型的引用存放在栈或者堆中,数据存放在堆中
image

注意:

如果值类型实例化在方法内,则会分配到栈中

如果值类型作为类型的成员,则会分配到堆中

.NET 中引用占用的内存大小:

4 bytes in a 32-bit environment

8 bytes in a 64-bit environment

A 32-bit processor will copy a 32-bit reference

A 64-bit processor will copy a 64-bit reference

实例:分配在栈中

struct Point { public int X, Y; }
void SomeMethod()
{
    Point p; // p will reside on the stack
}

实例:分配在堆中

class MyClass
{
   Point p; 
   // Lives on heap, because MyClass instances live on the heap
}

C#中预定义类型的分类(内存存储方式)

image

其他类型

null 只能赋给引用类型,表示未分配堆内存

void 在C#中只代表无返回值

隐式局部变量

说明:

隐式局部是特定类型变量的符号,只是一种速记的标记,最终会转为对应的类型

关键字:

var

注意:

​ 只能用于局部变量

​ 变量声明必须可以推断出类型,否则不可以使用

​ 一旦编译器推断出类型,类型就是固定的不可以进行更改了

​ 与JavaScript中var对应的C#标记是dynamic,不是C#中的var

​ 隐式局部变量需要.NET Framework 3版本以上才可以用

可空类型(Nullable)

可空类型(Nullable)说明

可空类型允许我们创建一个 值类型 并且可以标记为有效或无效

可空类型让值类型可以赋值null

可以为可空类型赋值以下值:

​ 对应基础类型的值、同类型可空类型的值、null

可空类型(Nullable)定义

int? myNInt = 28;

image

可空类型几个重要成员:

​ HasValue 指示值是否有效

​ Value 指示可空类型中基础类型相同类型并且返回变量的值

给可空类型赋值:

int? panda1 = 123;      //对应基础类型的值
int? padna2 = panda1;   //同类型的可空类型的值
int? padna3 = null;     //null

使用可空类型变量前记得检测一下是否为null:

int? myInt = 15;
if(myInt != null)
{
    Console.WriteLine(myInt);
}

或者使用HasValue属性进行检测可空类型变量是否有内容

int? panda1 = null;
if(panda1.HasValue)
{
    Console.WriteLine(panda1);
}
else
{
    Console.WriteLine("panda1 is null");
}

可空结构类型

定义可空结构类型

using System;
struct PandaStruct
{
    public int x;
    public int y;
    public PandaStruct(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

class Program
{
    static void Main()
    {
        PandaStruct? someVar = new PandaStruct(1, 2);
        if (someVar.HasValue)
        {
            Console.WriteLine(someVar);
            Console.WriteLine(someVar.Value.x);
            Console.WriteLine(someVar.Value.y);
            Console.WriteLine(someVar.HasValue);
            Console.WriteLine(someVar.Value);
        }
        
        Console.ReadKey();
    }
}

普通结构 和 可空结构 类型的区别:

可空结构需要通过使用value来访问其成员
image

static void Main()
{
    PandaStruct? someVar = new PandaStruct(1, 2);
    if (someVar.HasValue)
    {
        Console.WriteLine(someVar);
        Console.WriteLine(someVar.Value.x);
        Console.WriteLine(someVar.Value.y);
        Console.WriteLine(someVar.HasValue);
        Console.WriteLine(someVar.Value);
    }
    
    Console.ReadKey();
}

GetValueOrDefault() 方法

如果该引用的变量中存在值(hasValue = true),GetValueOrDefault()则返回值,否则返回一个类型的默认值

实例1:

//定义 nullable 变量
int? panda = 666;
//获得 值 或者 使用默认值
int data = panda.GetValueOrDefault();
Console.WriteLine(data); //666

实例2:

using System;
namespace ConsoleApp1
{
    struct PandaStruct
    {
        public int Code { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //定义 nullable 变量
            PandaStruct? panda = new PandaStruct();
            //获得 值 或者 使用默认值
            var data = panda.GetValueOrDefault();
            Console.WriteLine(data.Code);

            //wait
            Console.ReadKey();
        }
    }
}

Nullable类型转换(Implicit and Explicit Nullable Conversions)

从 值类型转 为 Nullable类型 可以直接隐式(implicit)转换

从 Nullable类型 转为 值类型 可以直接显式(explicit)转换

本质是使用Nullable实例对象的Value属性

注意:如果Nullable类型的值为null,转换将抛出 InvalidOperationException 异常

提示:可以使用as转换,避免抛出异常

实例:

int? x = 5;        // implicit
int y = (int)x;    // explicit

实例:

object p1 = "Panda";
int? p2 = p1 as int?; //转换
if(p2.HasValue)
{
    Console.WriteLine(p2.Value);
}
else
{
    Console.WriteLine("Not Exists");
}

Nullable类型运算符(Nullable Operator)(Operator lifting)

Nullable类型没有定义运算符的规则,但会转为底层的类型再进行比较处理

比较运算符,转为底层的类型进行比较(Relational operators (<, <=, >=, >))

实例:

int? x = 5;
int? y = 10;
bool b = x < y; // true
//as same
bool b = (x.HasValue && y.HasValue) ? (x.Value < y.Value) : false;

实例:

int? x = 5;
int? y = null;
// Equality operator examples
Console.WriteLine (x == y); // False
Console.WriteLine (x == null); // False
Console.WriteLine (x == 5); // True
Console.WriteLine (y == null); // True
Console.WriteLine (y == 5); // False
Console.WriteLine (y != 5); // True
// Relational operator examples
Console.WriteLine (x < 6); // True
Console.WriteLine (y < 6); // False
Console.WriteLine (y > 6); // False
// All other operator examples
Console.WriteLine (x + 5); // 10
Console.WriteLine (x + y); // null (prints empty line)

相等运算符(Equality operators)

Nullable类型相等运算符== and !=将遵循引用类型的比较

规则如下:

If exactly one operand is null, the operands are unequal

If both operands are non-null, their Values are compared

实例:

Console.WriteLine ( null == null); // True
Console.WriteLine ((bool?)null == (bool?)null); // True

其他运算符(+, −, *, /, %, &, |, ^, <<, >>, +, ++, --, !, ~)

These operators return null when any of the operands are null

实例:

int? c = x + y; // Translation:
int? c = (x.HasValue && y.HasValue)
 ? (int?) (x.Value + y.Value)
 : null;
// c is null (assuming x is 5 and y is null)

位运算符(bool? with & and | Operators)

实例:

bool? n = null;
bool? f = false;
bool? t = true;
Console.WriteLine (n | n); // (null)
Console.WriteLine (n | f); // (null)
Console.WriteLine (n | t); // True
Console.WriteLine (n & n); // (null)
Console.WriteLine (n & f); // False
Console.WriteLine (n & t); // (null)

可空类型本质

编译器是用System.Nullable<类型>泛型类为我们隐式创建可空类型

System.Nullable<类型>

注意:类型必须是值类型

我们也可以手动创建该类型:

PandaStruct? someVar = new PandaStruct(1, 2);

等同于:

Nullable<PandaStruct> someVar = new Nullable<PandaStruct>(new PandaStruct(1, 2));

实例:

using System;
class Program
{
    struct PandaStruct
    {
        public int x;
        public int y;

        public PandaStruct(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }
    static void Main()
    {
        Nullable<int> panda1 = new Nullable<int>();
        panda1 = 123;
        if(panda1.HasValue)
        {
            Console.WriteLine(panda1);
        }

        Nullable<PandaStruct> panda2 = new Nullable<PandaStruct>(new PandaStruct(1,2));
        if(panda2.HasValue)
        {
            Console.WriteLine(panda2.Value.x);
            Console.WriteLine(panda2.Value.y);
        }

        Console.ReadKey();
    }
}

实例:输出可空类型的默认值

int? thisCouldBeNull = null;
Console.WriteLine(thisCouldBeNull); //''
Console.WriteLine(thisCouldBeNull.GetValueOrDefault()); //0
thisCouldBeNull = 7;
Console.WriteLine(thisCouldBeNull); //7
Console.WriteLine(thisCouldBeNull.GetValueOrDefault()); //7

Nullable引用类型(可空的引用类型)(Nullable Reference Types)(C#8)

说明:

C#数据类型分为值类型和引用类型两种,值类型的变量不可以为空,而引用类型变量可以为空。
这就意味着如果不注意检查引用类型变量是否可空,就有可能造成程序中出现NullReferenceException异吊。

nullable引用类型(nullable reference types)作用 和 值类型的nuable相反,赋予引用类型,非null的能力,减少NullReferenceException异常。开启引用类型nullable后,引用类型必须标记为可为null才可以赋值为null,否则编译器报错。

作用:可以有效的避免NullReferenceException异常

本质:nullable reference types只是在编译时的语法糖

​ 在运行时(runtime),引用类型 和 引用类型?没有差别

使用方法:

csproj中<Nullable>enable</Nullable>启用可空引用类型检查。
在引用类型后添加“?”修饰符来声明这个类型是可空的。对于没有添加“?”修饰符的引用类型的变量,如果编译器发现存在为这个变量赋值null的可能性的时候,编译器会给出警告信息。

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

或者 配置文件级的Nullable,直接在代码中使用预处理指令

#nullable enable   // 开启
#nullable disable  // 关闭
#nullable restore  // 恢复使用配置文件配置信息

意味着使引用类型可以为null,必须以下方式声明引用类型:
引用类型?

实例1:

#nullable enable    // Enable nullable reference types
string s1 = null;   // Generates a compiler warning!
string? s2 = null;  // OK: s2 is nullable reference type

注意:开启可Nullable引用类型后,以下代码回报错

class Foo { string x; }

可以使用忽略null运算符(null-forgiving operator (!))

void Foo (string? s) => Console.Write (s!.Length);

也可以不使用忽略null运算符

void Foo (string? s)
{
  if (s != null) Console.Write (s.Length);
}

dynamic类型

说明

变量在编译期就可以确定类型的类型叫做 静态类型

但不是所有语言都是静态类型,比如:Python就是动态类型,变量运行时才进行解析

为了支持这种类型,使用dynamic关键字

动态类型类似JavaScript的类型

dynamic类型在编译期忽略类型检查,给dynamic类型的任何操作都可以编译通过

dynamic类型的目的是支持Dynamic Language Runtime(DLR),为动态语言提供支持

动态类型与Object类型不同在于:动态类型不进行编译为CIL时的静态类型检测

动态类型dynamic与var区别:var本质是强类型

动态类型没有智能提示,错误只能在运行时发现

如果绑定的类型或调用的成员不符合,会抛出RuntimeBinderException

主要用途

动态绑定适合于编译器不知道类型存在,但开发者知道类型存在的场景

动态绑定可以让编译器在编译阶段,跳过类型检查,等到运行时在检查

与动态语言进行交互:比如JavaScript、Python

简化后期绑定:预先不必知晓类型及其成员,运行时进行绑定运行实例

简化COM交互:PIA自动嵌入匹配COM类型

动态类型的可用与限制

可用:

可以作为方法的返回值、参数、局部变量;类型的成员,如字段、属性

限制:

​ 不可以用于Lambda表达式、匿名方法,但可以直接使用委托

​ 不可以使用扩展方法、LINQ

dynamic关键字

using System;

namespace PandaNamespace
{
    class PandaClass
    {
        static void Main(string[] args)
        {
            dynamic panda = "Panda666";
            Console.WriteLine(panda);
            panda = 666;
            Console.WriteLine(panda);

            Console.ReadKey();
        }
    }
}

ExpandoObject类型

除了可以通过dynamic定义后期绑定类型(动态类型)

还可以通过ExpandoObject类型创建后期绑定类型(动态类型)

实例:使用ExpandoObject类型创建动态类型

using System;
using System.Dynamic;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic dx = new ExpandoObject();
            dx.Name = "Panda666";
            dx.Website = "www.Panda666.com";

            Console.WriteLine(dx.Name);
            Console.WriteLine(dx.Website);

            //wait
            Console.ReadKey();
        }
    }
}

实例:将ExpandoObject类型转为字典类型

因为ExpandoObject类型继承了IDictionary接口

所以可以转为字典进行访问

using System;
using System.Collections.Generic;
using System.Dynamic;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic dx = new ExpandoObject();
            dx.Name = "Panda666";
            dx.Website = "www.Panda666.com";
            dx.Description = "www.Panda666.com";
            dx.Code = 666;

            IDictionary<string, object> d = dx;
            foreach (var item in d)
            {
                Console.WriteLine($"{item.Key}-{item.Value}");
            }

            //wait
            Console.ReadKey();
        }
    }
}

DynamicObject类型

ExpandoObject类型可以实现大部分后期绑定操作

有时候需要自定义动态类型,这时候就需要DynamicObject类型

通过继承DynamicObject类型来实现自定义类型

其中比较重要的几个成员:

TryGetMember方法,类似对类的属性进行设值

TrySetMember方法,类似对类的属性进行获取值

TryInvokeMember方法,类似类的方法调用

TryInvoke方法,类似委托对象的调用

TryBinaryOperation方法,类似运算符操作

TryGetIndex/TrySetIndex方法,类似类索引器调用

using System;
using System.Collections.Generic;
using System.Dynamic;

namespace ConsoleApp1
{
    public class PandaDynamicObject: DynamicObject
    {
        private IDictionary<string, object> _data = new Dictionary<string, object>();

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return _data.TryGetValue(binder.Name.ToLower(),out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            return _data.TryAdd(binder.Name.ToLower(), value);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            dynamic dv = new PandaDynamicObject();
            dv.panda = 666;
            dv.website = "panda666.com";

            Console.WriteLine(dv.panda);
            Console.WriteLine(dv.website);

            //wait
            Console.ReadKey();
        }
    }
}

静态绑定和动态绑定(Static Binding versus Dynamic Binding)

动态类型错误处理

步骤:

1、项目引入程序集Microsoft.CSharp.dll

2、项目引入Microsoft.CSharp.RuntimeBinder命名空间

using Microsoft.CSharp.RuntimeBinder;

3、使用RuntimeBinderException异常类处理动态绑定时错误

try
{
    dynamic a = 123;
    a.SS();
    Console.WriteLine(a);
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
{
    Console.WriteLine(e.Message);
}

DLR

说明:

​ 动态运行时

​ 动态类型的运行环境

​ 是CLR的补充

命名空间:

​ System.Dynamic

指针类型

C#中指针的主要作用

interacting with C Dynamic-Link Libraries [DLLs]
Component Object Model [COM])
dealing with data not in the main memory (such as graphics memory or a storage medium on an embedded device)

posted @ 2022-09-10 08:03  重庆熊猫  阅读(1318)  评论(0编辑  收藏  举报