C#关键字:in、out、ref、in T、out T、[In]、[Out]

in、out 和 ref 关键字

首先我们来说in、out 和 ref ,在 C# 中,in、out 和 ref 是用于方法参数的引用传递。在引用传递过程中,形参和实参都是指向相同的引用地址。

名称 作用 使用场景 是否需要提前初始化
in 只读参数,按引用传递 高效传递大对象但不希望修改其值 不需要
ref 输出参数,按引用传递 方法内部可以读取和修改参数值,调用前必须初始化 需要
out 输出参数,按引用传递 方法内部必须赋值,调用前不需要初始化 , 从方法返回多个值 不需要

in T和out T关键字

in T和out T主要用来修饰泛型接口和委托,用来实现协变和逆变。这两个概念很抽象,但是我觉得大家没必要去刻意理解这两个概念,意义不大。只需要知道,被in关键修饰的泛型接口,类型T只能用作输出参数,被out关键字修饰的泛型接口,类型T只能用作返回值。

名称 作用 使用场景
in T 用于泛型接口或委托,表示泛型类型参数是协变的,只能用于输入(只读) 限制类型T只能在形参(输入)中出现
out T 用于泛型接口或委托,表示泛型类型参数是逆变的,只能用于输出。 限制类型T只能在返回值(输出)中出现

[In]、[Out]特性

In\]、\[Out\]是C#中的两个特性Attribute。 在P/Invoke平台调用中会用到,但是这两个属性基本上都是可以被忽略。\[In\]特性在P/Invoke调用中,表示参数是输入参数。\[Ourt\]特性在P/Invoke调用中,表示参数是输出参数。 | 名称 | 作用 | 使用场景 | |-------|-----------------------------------|--------------------| | in T | 用于泛型接口或委托,表示泛型类型参数是协变的,只能用于输入(只读) | 限制类型T只能在形参(输入)中出现 | | out T | 用于泛型接口或委托,表示泛型类型参数是逆变的,只能用于输出。 | 限制类型T只能在返回值(输出)中出现 | ## in 关键字 In 关键字用于指示方法的参数是只读的,不可以在方法内部通过=号直接对参数进行赋值操作,但是可以修改对象本身的属性。in关键字主要使用场景:需要高效传递大对象,但不希望修改其值的场景。 下面代码中,在`PrintValue`方法中,不能直接对形参`person`赋值,但是可以修改`person`的属性`Age`。 ```arduino class Program { static void Main(string[] args) { Person p = new Person() { Age = 3 }; Console.WriteLine("执行方法之前:" + p.Age); PrintValue(in p); Console.WriteLine("执行方法之后:" + p.Age); } static void PrintValue(in Person person) { //person = new Person(); // 编译错误,被in参数修饰的参数不能被修改 person.Age = 20; // 编译成功,允许修改对象的属性、字段等 } class Person { public int Age { get; set; } } } ``` ## ref 关键字 in关键字标识形参为只读变量,在方法中不可赋值。ref在in关键字的参数的基础上,允许方法修改形参。这意味着在方法内部对形参所做的任何修改,都会反映到方法外部的原始变量上。 我们将上面的代码中in关键字,改为ref关键字,并且在方法内部修改形参的值。对形参的任何修改都会导致调用者变量`person`发生变化。 ```csharp class Program { static void Main(string[] args) { Person p = new Person() { Age = 3 }; Console.WriteLine("执行方法之前:" + p.Age); PrintValue(ref p); Console.WriteLine("执行方法之后:" + p.Age); } static void PrintValue(ref Person person) { person = new Person() { Age=25}; } class Person { public int Age { get; set; } } } ``` 有人可能有疑问,C#有值传递(拷贝传递)和引用传递(地址传递)两种方式,对于class对象来说,本来就是引用传递,再加一个ref岂不是多此一举?实时并非如此。如果没有ref关键字修饰,如果在方法内创建一个新对象并赋值给形参,将切断形参与实参之间的关联,并且方法调用结束后,新对象也将不复存在。 我们可以将ref关键字去掉,重新执行,可以看到 以下执行效果:执行方法前后打印的结果都是3,在方法中对形参的修改,并没有反应到调用者变量p。 ref参数注意事项: 1、参数在方法内部是可读可写的。 2、在使用 ref 时,传递给方法的参数必须在传递之前进行初始化。 3、适用于需要在方法内部修改参数值的场景。 ## out关键字 out关键字同样也是引用传递,和ref不同的是,它可以将参数直接返回,所以out关键字的使用场景主要是:需要参数返回多个值。 下面代码中,`PrintValue`返回两个参数flag和str,我们在main函数中成功将方法返回的参数打印出来。 ```csharp class Program { static void Main(string[] args) { PrintValue(out int flag, out string str); Console.WriteLine("flag:" + flag + ",str:" + str); } static void PrintValue(out int flag, out string str) { flag = 0; str = "hello"; } } ``` ## in T(泛型修饰符) 作用:用于泛型接口或委托,表示泛型类型参数是协变的,只能用于输入(只读)。 特点:协变允许将派生类型传递给基类型的泛型参数。 使用场景:适用于只需要读取泛型类型的场景。 我们来看一个接口声明:接口`IProcessor`是一个泛型接口,类型T用in关键字修饰,那么这个T类型只能出现在方法的参数中,也就是输入,不能作为方法的返回值。 ```csharp interface IProcessor { void Process(T item); } ``` 如果我们将T作为返回值,会直接编译失败,请看下面的报错。 ![](https://oss.xyyzone.com/jishuzhan/article/1911662406163562498/8ba62f312b41048172f97bda13c9cffa.webp) ## out T关键字(泛型修饰符) 作用:用于泛型接口或委托,表示泛型类型参数是逆变的,只能用于输出。 特点:逆变允许将基类型传递给派生类型的泛型参数。 使用场景:适用于只需要返回泛型类型的场景。 请看以下代码,这是一个工厂模式的接口,`IFactory`是一个泛型接口,类型T用out关键字修饰之后,那么T类型只能用作接口的返回值。 ```csharp interface IFactory { T Create(); } ``` 当我们将T用作形参输入的时候,会直接编译失败,请看下图: ![](https://oss.xyyzone.com/jishuzhan/article/1911662406163562498/7bebfc985e1b9116601002da4ec5d3fd.webp) ## \[In\](参数属性) 作用:用于 P/Invoke(平台调用),表示参数是输入参数。 特点:通常与 ref 或 out 一起使用,明确指定参数的方向。 使用场景:用于与非托管代码交互时,指定参数的方向。 请看以下的P/Invoke调用,我们通过调用Windows C++的Api弹出对话框,其中,输入参数我们是可以用\[In\]关键字修饰的,这个通常情况下可以忽略不写。 ```csharp [DllImport("user32.dll")] static extern int MessageBox([In] IntPtr hWnd, [In] string text, [In] string caption, uint type); ``` ## \[Out\](参数属性) 作用:用于 P/Invoke(平台调用),表示参数是输入参数。 特点:通常与 ref 或 out 一起使用,明确指定参数的方向。 使用场景:用于与非托管代码交互时,指定参数的方向。 请看以下的P/Invoke调用,当我们调用`GetComputerName` Api的时候,Api会返回计算机名称,但是这个名称是在`lpBuffer`变量中返回的。也就是说,在方法的内部对`lpBuffer`变量进行了赋值。这个参数我们把它叫做输出参数。用\[Out\]修饰。 ```csharp [DllImport("kernel32.dll", SetLastError = true)] static extern bool GetComputerName([Out] StringBuilder lpBuffer, ref uint nSize); ```

相关推荐
m0_7482480228 分钟前
C++与C#布尔类型深度解析:从语言设计到跨平台互操作
c++·stm32·c#
LeonDL1682 小时前
【通用视觉框架】基于C#+VisionPro开发的视觉框架软件,全套源码,开箱即用
人工智能·c#·visionpro·通用视觉框架·机器视觉框架·视觉框架软件·机器视觉软件
一抓掉一大把3 小时前
RuoYi .net-实现商城秒杀下单(redis,rabbitmq)
redis·mysql·c#·rabbitmq·.net
睡前要喝豆奶粉3 小时前
在.NET Core Web Api中使用阿里云OSS
阿里云·c#·.netcore
缺点内向10 小时前
C#: 高效移动与删除Excel工作表
开发语言·c#·.net·excel
yue00812 小时前
C# 分部类读取学生信息
开发语言·c#
聪明努力的积极向上12 小时前
【C#】事件简单解析
开发语言·c#
qq_124987075313 小时前
基于C#的贵州省黔北地区乡村避暑生活共享平台设计与实现(源码+论文+部署+安装)
c#·毕业设计·asp.net·生活
LateFrames18 小时前
C# 中,0.1 在什么情况下不等于 0.1 ?
开发语言·c#
mudtools1 天前
解放双手!使用Roslyn生成代码让你的 HTTP 客户端开发变得如此简单
低代码·c#·.net