C#教程 - C#数据类型(Data Type)
更新记录
转载请注明出处。
2022年9月10日 发布。
2022年9月10日 从笔记迁移到博客。
数据类型(Data Type)概念
简单说就是:值的蓝图,某种值数据的特征。约等于数据的物理结构 + 数据的逻辑结构 + 数据操作行为。数据的物理结构决定了如何将值存储到计算机的内存中,数据的逻辑结构决定了如何使用该值,数据操作行为决定了值可以进行的操作。
为什么分成不同的数据类型
更高效的进行内存分配、数据存储、数据处理
预定义类型(Predefined Type)(built-in types)
说明
所有 预定义类型 都直接隐射到底层的 .NET Runtime类型。C#提供16种预定义类型,13种简单类型,3种非简单类型。
注意:预定义类型关键字全是小写。
预定义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 使用动态语言编写的程序集使用
预定义类型详细信息
数据类型分类(大类)
Value types(值类型)
Reference types(引用类型)
Generic type parameters(泛型类型参数)
Pointer types(指针类型)
在.NET中的预定义类型继承结构图
数值类型细节(整型、浮点型)
数值类型细节注意
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都无法非常精确的表示无限循环小数
布尔类型细节注意
布尔类型占用空间注意
虽然布尔类型只需要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 //无符号长整型
字面量数值分隔符(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;
字符字面量
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,后进先出)的数据结构,栈存储以下类型:
传递给方法形参的值
局部变量的值
程序当前的执行环境信息
堆(Heap)
堆(Heap)是一块内存区域,一般在堆区分配大块的内存用于存储对象
一般情况下,C#中不可以显式删除堆中数据,由CLR的GC自动进行删除回收内存
GC自动回收示意图
数据类型分类(按内存存储方式)
值类型(VALUE TYPES) 和 引用类型(REFERENCE TYPES)
值类型:
只需要一段存储空间,直接用于存储实际的数据
值类型的数据存放在栈中
值类型派生自System.ValueType
System.ValueType重写了Object类型中的所有虚成员(Virtual Members)
引用类型:
需要两段内存空间,一段内存用于存储引用,一段内存用于实际数据
引用指向实际数据地址
引用类型的引用存放在栈或者堆中,数据存放在堆中
注意:
如果值类型实例化在方法内,则会分配到栈中
如果值类型作为类型的成员,则会分配到堆中
.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#中预定义类型的分类(内存存储方式)
其他类型
null 只能赋给引用类型,表示未分配堆内存
void 在C#中只代表无返回值
隐式局部变量
说明:
隐式局部是特定类型变量的符号,只是一种速记的标记,最终会转为对应的类型
关键字:
var
注意:
只能用于局部变量
变量声明必须可以推断出类型,否则不可以使用
一旦编译器推断出类型,类型就是固定的不可以进行更改了
与JavaScript中var对应的C#标记是dynamic,不是C#中的var
隐式局部变量需要.NET Framework 3版本以上才可以用
可空类型(Nullable)
可空类型(Nullable)说明
可空类型允许我们创建一个 值类型 并且可以标记为有效或无效
可空类型让值类型可以赋值null
可以为可空类型赋值以下值:
对应基础类型的值、同类型可空类型的值、null
可空类型(Nullable)定义
int? myNInt = 28;
可空类型几个重要成员:
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来访问其成员
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)
本文来自博客园,作者:重庆熊猫,转载请注明原文链接:https://www.cnblogs.com/cqpanda/p/16675931.html