C# 类型系统

news2024/11/19 15:27:55

1. 隐式类型

c#允许使用 var 声明变量,编译期会通过初始化语句右侧的表达式推断出变量的类型。

// i is compiled as an int
var i = 5;

// s is compiled as a string
var s = "Hello";

// a is compiled as int[]
var a = new[] { 0, 1, 2 };

// expr is compiled as IEnumerable<Customer>
// or perhaps IQueryable<Customer>
var expr =
    from c in customers
    where c.City == "London"
    select c;

// anon is compiled as an anonymous type
var anon = new { Name = "Terry", Age = 34 };

// list is compiled as List<int>
var list = new List<int>();

使用var来声明变量,我们可以把注意力放在更为重要的使用部分,只需要准确的变量名,既提供正确的语义,剩下的编译期会去操心变量的类型与后面的使用方法是不是匹配。

甚至有的时候,开发者可能会定义错误或者不合适的数据类型,反而不如var方式声明隐式类型。 如 C# :IQueryable & IEnumerable 中:

var q = from c in dbContext.Customers Where c.City == "London" select c;
var finalAnswer = from c in q order by c.Name select c;

上面的 q 编译期会通过表达式,将 q 退段位 IQueryable 类型,从而将 Where 表达式与第二行的 order by 进行表达树组合,在一次sql查询操作里完成。

假如开发者明确声明了 q 的类型,但是声明为 IEnumerable,那么在第一行结束,就会把 Where 查询到的所有数据传到本地,之后再进行排序。

IEnumerable<Customer> q = from c in dbContext.Customers Where c.City == "London" select c;
var finalAnswer = from c in q order by c.Name select c;

隐式类型的数据转换

在使用隐式类型的时候,需要注意的是类型转换的问题。

有些转换是宽化转化(widening conversion),有些则是窄化转换(narrowing conversion),窄化转换会导致精度下降,例如从 long 到 int 的转换。

    public class VarTypeConvert
    {
        public static void Run()
        {
            IMagicNumberGenerator<double> doubleMNGenerator = new DoubleMNGenerator();
            IMagicNumberGenerator<float> floatMNGenerator = new FloatMNGenerator();
            IMagicNumberGenerator<int> intMNGenerator = new IntMNGenerator();

            var d = doubleMNGenerator.GenerateMagicNumber();
            var dtotal = 100 * d / 6;
            Console.WriteLine($"Double magic number: {d}, Total: {dtotal}");
            
            var f = floatMNGenerator.GenerateMagicNumber();
            var ftotal = 100 * f / 6;
            Console.WriteLine($"Float magic number: {f}, Total: {ftotal}");

            var i = intMNGenerator.GenerateMagicNumber();
            var itotal = 100 * i / 6;
            Console.WriteLine($"Int magic number: {i}, Total: {itotal}");
        }
    }

-----------------Output--------------------------
Double magic number: 1, Total: 16.666666666666668
Float magic number: 1, Total: 16.666666
Int magic number: 1, Total: 16

都是计算 100 * x / 6,但是由于 x 的类型不同,隐式类型的公式结果大不相同。由于代码采用了隐式类型的局部变量,因此编译期自己推断变量的类型,即根据赋值符号右边的部分做出最佳选择。

即使我们将公式结果定义为 double,由于 MagicNumbe 是int类型,程序仍然会按照整数运算规则进行计算:

var i = intMNGenerator.GenerateMagicNumber();
double dtotal = 100 * i / 6;
Console.WriteLine($"Int magic number: {i}, Total: {dtotal}");
// Output: Int magic number: 1, Total: 16

2. 匿名类型

隐式类型的局部变量是为了支持匿名类型机制而加入 c# 语言的。如下匿名类型:

var v = new { Amount = 108, Message = "Hello" };
Console.WriteLine(v.Amount + v.Message);

我们在使用查询表达式的时候,常会使用匿名类型,从而对每个对象的属性子集处理和返回:

var productQuery =
from prod in products
select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

匿名类型为我们提供了一种将只读属性封装到单个对象,又不需要显示先定义类型的便捷方法。类名由编译期生成,每个属性的类型由编译期推断

匿名类型支持采用 with 表达式形式的非破坏性修改,从而使开发者可以创造匿名类型的新实例。

var p1 = new NamedPoint("A", 0, 0);
Console.WriteLine($"{nameof(p1)}: {p1}");  // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }
        
var p2 = p1 with { Name = "B", X = 5 };
Console.WriteLine($"{nameof(p2)}: {p2}");  // output: p2: NamedPoint { Name = B, X = 5, Y = 0 }     

值得注意的是,匿名类的 EqualsGetHashCode 方法是根据属性的 EqualsGetHashCode 定义的,因此仅当同一匿名类型两个实例所有属性都相等,这两个实例相等。

3. 编译时类型 和 运行时类型

编译时类型即直接声明的类型,或推断类型(c# 隐式类型)。

需要注意的是隐式类型的变量,编译器推断出来的类型不一定是我们期望的类型,比如:

// 推断类型为字符串
var message1 = "This is a string of characters";

// 期望类型为 字符的枚举数组
IEnumerable<char> message2 = "This is a string of characters";

运行时类型则是在代码运行阶段实际使用的类型。编译时类型确定编译期执行的所有操作,包括方法调用解析、重载决策以及可用的隐式显示强制转换。运行时类型确定在运行时解析度所有操作,包括调度虚拟方法、计算 is 和 switch 表达式以及其他类型测试API。

object o = Factory.GetObject();
MyType t = o as MyType;
// as 在运行时判断是否是 MyType,如果是则进行类型转换,否则返回 null
if (t != null) ...
else ...

再来看一个编译时类型和运行时类型在类型转换时的例子:

namespace LearnCSharp.Type
{
    public class MyType
    {
        public int data { get; set; }
    }

    public class SecondType
    {
        private MyType _value;

        public SecondType()
        {
            _value = new MyType()
            {
                data = 10
            };
        }

        // 定义类型转换从 SecondType -> MyType
        public static implicit operator MyType(SecondType st)
        {
            return st._value;
        }
    }

    public class LTypeFactory
    {
        public static SecondType GetObject()
        {
            return new SecondType();
        }
    }

    // 测试类型转换
    public class Program
    {
        public static void Main(string[] args)
        {
            SecondType secondType = new SecondType();
            // 根据SecondType 中的声明,可以成功转换类型
            MyType myType = secondType;
           Console.WriteLine($"Convert SecondType to MyType success: Data = {myType.data}");

            object o = LTypeFactory.GetObject();
            MyType t = o as MyType; // 由于运行时类型并不是 MyType,as运算符只会判断运行期类型,不会执行强制类型转换
            if (t != null)
            {
                Console.WriteLine($"Convert object to MyType success: Data = {t.data}");
            }
            else 
            {
                Console.WriteLine("Convert object to MyType failed");
            }

            try
            {
                MyType t1;
                t1 = (MyType) o;
                Console.WriteLine($"Convert object to MyType success: Data = {t1.data}");           
            }
            catch (Exception ex) // 强制类型转换以对象的编译期类型为依据,所以会认为 o 是 object,而非 SecondType,从而强制转换失败
            {
                Console.WriteLine($"Convert object to MyType failed: {ex.Message}");
            }

            VarTypeConvert.Run();
        }
    }
}

--------------------Output----------------------------------
Convert SecondType to MyType success: Data = 10
Convert object to MyType failed
Exception thrown: 'System.InvalidCastException' in LearnCSharp.dll
Convert object to MyType failed: Unable to cast object of type 'LearnCSharpType.SecondType' to type 'LearnCSharp.Type.MyType'.

4. 装箱和取消装箱操作

装箱是将值类型转换为object类型,或由此值类型实现的任何接口类型的过程,新创建的引用对象相当于一个箱子,分配在堆上面,其中含有原值的一份拷贝

取消装箱则是从对象中提取值类型。会把已经装箱的那个值拷贝一份出来。

相对于简单的赋值而言,装箱和取消装箱过程需要大量的计算,装箱时,需要分配构造新对象。取消装箱所需要的强制转换也需要大量计算。

int i = 123;
object o = i;  // explicit boxing

image.png

int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;   // unboxing

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1720639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人脸识别模型与人类视觉识别的对比——评估人脸识别模型存在偏见是否比人类的偏见大?

1. 概述 人脸识别系统是一个几十年来一直备受关注的研究领域。而且在过去的几年中。公司和政府一直在积极引入人脸识别系统&#xff0c;并且我们看到越来越多的机会可以看到人脸识别系统。例如&#xff0c;有的系统可以随便介绍&#xff0c;如搜索特定人的图像&#xff08;图像…

关系数据库:关系运算

文章目录 关系运算并&#xff08;Union&#xff09;差&#xff08;Difference&#xff09;交&#xff08;Intersection&#xff09;笛卡尔积&#xff08;Extended Cartesian Product&#xff09;投影&#xff08;projection&#xff09;选择&#xff08;Selection&#xff09;除…

[Linux]vsftp配置大全---超完整版

[Linux]vsftp配置大全---超完整版 以下文章介绍Liunx 环境下vsftpd的三种实现方法 一、前言 Vsftp(Very Secure FTP)是一种在Unix/Linux中非常安全且快速稳定的FTP服务器&#xff0c;目前已经被许多大型站点所采用&#xff0c;如ftp.redhat.com,ftp.kde.org,ftp.gnome.org.等。…

switch语句

作用 让顺序执行的代码&#xff0c;产生分支。 基本语法 switch(变量) {//变量 常量 执行 case和 break之间的代码case 常量:满足条件执行的代码逻辑;break;case 常量:满足条件执行的代码逻辑;break;//case 可以有无数个default://如果上面case的条件都不满足 就会执行 def…

js四舍五入和计算精度问题处理

js四舍五入和计算精度问题处理 目录 js四舍五入和计算精度问题处理错误计算方法示例代码 js中加减乘除&#xff0c;部分数据会存在计算不准确。 错误计算 我使用的是big.js&#xff0c;基于big.js库封装了下工具方法&#xff0c;当然也可以用其他库&#xff0c;如mathjs/bignu…

【学习笔记】计算机组成原理(九+十)

控制单元的功能 文章目录 控制单元的功能9.1 微操作命令的分析9.1.1 取指周期9.1.2 间址周期9.1.3 执行周期9.1.4 中断周期 9.2 控制单元的功能9.2.1 控制单元的外特性9.2.2 控制信号举例9.2.3 多级时序系统9.2.4 控制方式 控制单元的设计10.1 组合逻辑设计10.1.1 组合逻辑控制…

04-树5 Root of AVL Tree(浙大数据结构PTA习题)

04-树5 Root of AVL Tree 分数 25 作者 陈越 单位 浙江大学 An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the two child subtrees of any node differ by at most one; if at any time they differ by more th…

Matlab|基于粒子群算法优化Kmeans聚类的居民用电行为分析

目录 主要内容 部分代码 结果一览 下载链接 主要内容 在我们研究电力系统优化调度模型的过程中&#xff0c;由于每天负荷和分布式电源出力随机性和不确定性&#xff0c;可能会优化出很多的结果&#xff0c;但是经济调度模型试图做到通用策略&#xff0c;同样的策…

Java-集合基础

集合 一、含义 集合是Java API所提供的一系列类&#xff0c;可以用于动态存放多个对象 (集合只能存对象)集合与数组的不同在于&#xff0c;集合是大小可变的序列&#xff0c;而且元素类型可以不受限定&#xff0c;只要是引用类型。(集合中不能放基本数据类型&#xff0c;但可以…

WPF Binding对象

在WinForm中&#xff0c;我们要想对控件赋值&#xff0c;需要在后台代码中拿到控件对象进行操作&#xff0c;这种赋值形式&#xff0c;从根本上是无法实现界面与逻辑分离的。 在WPF中&#xff0c;微软引入了Binding对象&#xff0c;通过Binding&#xff0c;我们可以直接将控件与…

从零开始利用MATLAB进行FPGA设计(七)用ADC采集信号教程2

黑金的教程做的实在太拉闸了&#xff0c;于是自己摸索信号采集模块的使用方法。 ADC模块&#xff1a;AN9238 FPGA开发板&#xff1a;AX7020&#xff1b;Xilinx 公司的 Zynq7000 系列的芯片XC7Z020-2CLG400I&#xff0c;400引脚 FBGA 封装。 往期回顾&#xff1a; 从零开始利…

【易错题】数据统计补充习题(选择题 )#CDA Level 1

本文整理了数据统计相关的易错题&#xff0c;部分可作为备考CDA Level 1统计学部分的补充习题。来源&#xff1a;如荷学题库&#xff08;CFDP第三部分&#xff09; 1&#xff09; 2&#xff09; 3&#xff09; 4&#xff09; 5&#xff09; 6&#xff09; 7&#xff09; 8&…

解决Mac ~/.bash_profile 配置的环境变量重启终端后失效问题

在Mac系统中&#xff0c;配置环境变量通常是在~/.bash_profile文件中进行。然而&#xff0c;有时会遇到配置的环境变量在重启终端后失效的问题。 解决办法&#xff1a; 在~/.zshrc文件最后或最前面&#xff0c;增加一行 source ~/.bash_profile

记一次netty客户端的开发

背景 近日要开发一个tcp客户端程序去对接上游厂商的数据源&#xff0c;决定使用netty去处理&#xff0c;由于很久没有开发过netty了&#xff0c;顺便学习记录下 netty搭建 考虑到我们需要多个client去对接server服务&#xff0c;所以我们定义一个公共的AbstractNettyClient父…

RAID技术迭代、原理对比、产品梳理(HCIA)

目录 一、RAID技术迭代 传统RAID LUN虚拟化2.0 工作原理&#xff1a; 块虚拟化2.0 为什么有RAID2.0&#xff1f; RAID2.0实现原理&#xff1a; RAID-TPRAID 7 华为RAID-TP技术 RAID的4种工作状态 RAID算法 普通RAID算法 华为动态RAID算法 保险箱盘&#xff08;存掉…

Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks 阅读笔记

这才是真正RAG&#xff0c;如果只是把检索得到结果放到prompt里面&#xff0c;可能够呛。 好久没有读paper了&#xff0c;最近因为有个小工作&#xff0c;来读一篇较早提出来RAG想法的文章吧。这篇文章是Facebook、伦敦大学学院以及纽约大学的研究者们搞出来的。文章首先指出&a…

8-异常与错误

8-异常与错误 1、简介2、异常处理2.1 抛出异常2.2 捕获异常2.3 匹配顺序 3、异常说明4、构造函数中的异常5、析构函数中的异常6、标准库异常 1、简介 在程序编码过程中难免会出现错误&#xff0c;主要有&#xff1a;语法错误、逻辑错误、功能错误等&#xff0c;当我们面对以上…

SpringBoot打war包并配置外部Tomcat运行

简介 由于其他原因&#xff0c;我们需要使用SpringBoot打成war包放在外部的Tomcat中运行,本文就以一个案例来说明从SpringBoot打war包到Tomcat配置并运行的全流程经过 环境 SpringBoot 2.6.15 Tomcat 8.5.100 JDK 1.8.0_281 Windows 正文 一、SpringBoot配置打war包 第一步&a…

echarts 图表不显示的问题

是这样的&#xff0c;点击详情&#xff0c;再点击统计&#xff0c;切换的时候就不会显示echarts图表&#xff0c;刚开始使用的是next Tick&#xff0c;没有使用定时器&#xff0c;后来加上了定时器就实现了如下所示&#xff1a; 代码是如下 const chartContainer ref(null); …

开发一个SDK(starter)

1.创建项目 将pom.xml中build删除掉