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

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发生变化。

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函数中成功将方法返回的参数打印出来。

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类型只能出现在方法的参数中,也就是输入,不能作为方法的返回值。

interface IProcessor<in T>
{
void Process(T item);
}

如果我们将T作为返回值,会直接编译失败,请看下面的报错。

out T关键字(泛型修饰符)

作用:用于泛型接口或委托,表示泛型类型参数是逆变的,只能用于输出。
特点:逆变允许将基类型传递给派生类型的泛型参数。
使用场景:适用于只需要返回泛型类型的场景。

请看以下代码,这是一个工厂模式的接口,IFactory是一个泛型接口,类型T用out关键字修饰之后,那么T类型只能用作接口的返回值。

interface IFactory<out T>
{
T Create();
}

当我们将T用作形参输入的时候,会直接编译失败,请看下图:

[In](参数属性)

作用:用于 P/Invoke(平台调用),表示参数是输入参数。
特点:通常与 ref 或 out 一起使用,明确指定参数的方向。
使用场景:用于与非托管代码交互时,指定参数的方向。

请看以下的P/Invoke调用,我们通过调用Windows C++的Api弹出对话框,其中,输入参数我们是可以用[In]关键字修饰的,这个通常情况下可以忽略不写。

[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]修饰。

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetComputerName([Out] StringBuilder lpBuffer, ref uint nSize);

C#关键字:in、out、ref、in T、out T、[In]、[Out]这些你都知道多少?的更多相关文章

  1. 关键字:this、ref、out

    Class1.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; usin ...

  2. C# 中三个关键字params,Ref,out

    一. using System; using System.Collection.Generic; using System.Text; namespace ParamsRefOut { class ...

  3. volatile关键字?MESI协议?指令重排?内存屏障?这都是啥玩意

    一.摘要 三级缓存,MESI缓存一致性协议,指令重排,内存屏障,JMM,volatile.单拿一个出来,想必大家对这些概念应该有一定了解.但是这些东西有什么必然的联系,或者他们之间究竟有什么前世今生想 ...

  4. C# 参考之方法参数关键字:params、ref及out

    如果在为方法声明参数时未使用 ref 或 out,则该参数可以具有关联的值.可以在方法中更改该值,但当控制传递回调用过程时,不会保留更改的值.通过使用方法参数关键字,可以更改这种行为. params ...

  5. C#中关键字ref与out的区别【转】

    在C#中,ref与out是很特殊的两个关键字.使用它们,可以使参数按照引用来传递.总的来说,通常我们向方法中传递的是值.方法获得的是这些值的一个拷贝,然后使用这些拷贝,当方法运行完毕后,这些拷贝将被丢 ...

  6. c#关键字及ref和out

    最近在写程序时遇到ref,out 参数问题.回头有自习看了看MSDN,才有巩固了基础.我把我的测试程序贴出来,大家分享一下.    ref 关键字使参数按引用传递.其效果是,当控制权传递回调用方法时, ...

  7. C#方法参数传递-同时使用ref和out关键字

    在方法参数传递中,可以同时使用ref和out关键字,但是要注意ref和out参数传递的不同. using System;class Program{static void Main(){    Pro ...

  8. c# in out ref关键字

    class in_out_ref { #region in 关键字 delegate void DContravariant<in A>(A argumen); static void o ...

  9. C#学习历程(六)[ref 关键字的使用]

    ref 关键字的使用 ref 关键字通过引用(而非值)传递参数. 通过引用传递的效果是,对所调用方法中的参数进行的任何更改都反映在调用方法中. 例如,如果调用方传递本地变量表达式或数组元素访问表达式, ...

  10. C#:ref关键字和out关键字的区别

    1.在不使用关键字(比如ref关键字.out关键字等)修饰函数的情况下,大部分函数的参数是以值传递的方式,也就是说,“调用函数”在使用参数(比如myNumber)时,是把该参数复制多一份,然后将其传递 ...

随机推荐

  1. selenium等待的三种方式(详细)

    1.强制等待 time.sleep(3) 这种方式会是操作强行等待3s才会进行下一步操作,但是这种放法,可能会延长测试的时间,如果元素在1s中出现,就会浪费2s的时间,并且这种放法单次有效,每次需要等 ...

  2. Python语法使用

    由于之前学习过js,代码基本上是相同的,先看看和js有那些区别 项目 python javascript 适用版本 python3 es6,即ECMAScript 2015 运行环境 #!/usr/b ...

  3. 微信小程序block的作用

    有了block标签过后,你就可以把if 或则 for 语句写在block标签里面; 这样就控制了这一块的逻辑. 个人建议是要是v-if和v-for的都可以写在block上: block并不是一个组件, ...

  4. 内容分发网络 CDN 概述

    本文分享自天翼云开发者社区<内容分发网络 CDN 概述>,作者:Jerry CDN(Content Delivery Network)是一种分布式网络架构,旨在提供高效.可靠地将内容传送给 ...

  5. [记录点滴]Spring Boot Admin源码分析笔记

    [记录点滴]Spring Boot Admin源码分析笔记 0x00 摘要 本文是过去使用Spring Boot Admin时候分析源码的笔记.虽然比较简单,但是也可以看出Spring Boot Ad ...

  6. FLink自定义Sink,生产的数据导出到mysql

    一.自定义生产数据 https://www.cnblogs.com/robots2/p/16048729.html 二.生产转化数据,导出到mysql 2.1 建表语句 CREATE TABLE `v ...

  7. StarUML画时序图

    一.打开软件,新建时序图 二.画图 2.1 新建用户图标 2.2 新建几个生命线Lifeline 2.3 建立连接关系 2.4 图例

  8. Atcoder [AGC006D] Median Pyramid Hard 题解 [ 紫 ] [ 二分 ] [ adhoc ]

    Median Pyramid Hard:二分 trick 加上性质观察题. trick 我们可以二分值域,然后把大于等于它的数标记成 \(1\),其他标记为 \(0\)(有些题需要标记成 \(-1\) ...

  9. 「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!

    > 由于网页编辑器简陋,无法实现原文档的精心排版,如需原文档可联系... 序 「ximagine」在本篇文章中将介绍「荒岛」目前所使用的显示器测试流程及标准,我们主要使用Calman.Displ ...

  10. 赶上AI的大潮:在VSCode中使用DeepSeek编程的极简方法

    1 赶上AI的大潮:在VSCode中使用DeepSeek编程的极简方法 1.1 背景   DeepSeek在春节期间突然大行其道,欣喜国力大增的同时,对于普通IT工作者,如何才能享受这一波AI红利,让 ...