【.NET8】访问私有成员新姿势UnsafeAccessor(上)
前言
前几天在.NET性能优化群里面,有群友聊到了.NET8新增的一个特性,这个类叫UnsafeAccessor,有很多群友都不知道这个特性是干嘛的,所以我就想写一篇文章来带大家了解一下这个特性。
其实在很早之前我就有关注到这个特殊的特性,但是当时.NET8还没有正式发布,所以我也没有写文章,现在.NET8已经RC了,很快就会发布正式版,而且刚好有了一些时间,所以也可以带大家了解一下这个新的特性。
由于篇幅原因,这篇文章会分为上下两篇,其中上篇会带大家了解UnsafeAccessor是干嘛的,有哪些用法,下篇会带大家了解UnsafeAccessor的性能比较以及它的实现原理。
首先在我们了解这个类之前要假设一个场景,在很多时候我们都会遇到这样的场景,就是我们需要在一个类中访问另外一个类的私有成员,比如有如下代码:
var a = new A();
Console.WriteLine(a._value);
public class A
{
private int _value = 10;
}
在上面的代码中,我们在类B中访问了类A的私有成员_value,这种情况在我们的实际开发中是很常见的,但是在.NET中是不允许的,因为私有成员是不允许被外部访问的,所以我们在类B中是不能访问类A的私有成员_value的,但是在实际的开发中,我们有时候确实需要访问类A的私有成员_value,这个时候我们该怎么办呢?下面我们来看一下如何访问私有成员。
.NET8以前的解决方案
在.NET8之前,我们可以通过如下的几种方法来访问私有成员,分别是反射、Emit、Expression,下面我们分别来看一下这几种方法。
反射
在.NET中,有一种叫反射的技术,这个对于任何一个.NET开发工程师应该都不陌生,我们可以通过反射来访问程序集的元数据信息,调用方法,访问字段等等,所以可以通过反射来访问私有成员,比如下面的代码我们可以通过反射来访问私有成员,如下所示:
var a = new A();
// 反射访问私有成员
var value = typeof(A).GetField("_value", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(a);
Console.WriteLine(value);
public class A
{
private int _value = 10;
}
在上面的代码中,我们通过反射来访问了类A的私有成员_value,这样就可以访问到了,但是这种方式有一个缺点,就是性能比较差,因为反射是通过查找元数据和临时调用来实现的,所以性能比较差。在实际的开发中,我们一般不会使用反射来访问私有成员。
Emit
Emit 是 .NET 提供的一种动态生成和编译代码的技术。通过 Emit,我们可以动态生成一个新的方法,这个方法可以直接访问私有成员,这样就避免了反射的性能问题。以下是一个使用 Emit 访问私有成员的例子:
var a = new A();
// 创建一个动态方法,签名为 int GetValue(A a)
var method = new DynamicMethod("GetValue", typeof(int), new Type[] { typeof(A) }, typeof(A));
// 获取方法的 ILGenerator,通过 Emit 生成方法体
var il = method.GetILGenerator();
// 将参数 a 的私有成员 _value 压入堆栈
il.Emit(OpCodes.Ldarg_0);
// 将私有成员 _value 压入堆栈
il.Emit(OpCodes.Ldfld, typeof(A).GetField("_value", BindingFlags.NonPublic | BindingFlags.Instance));
// 返回栈顶的值
il.Emit(OpCodes.Ret);
// 通过 Emit 创建的方法,可以直接访问私有成员 _value
var func = (Func<A, int>)method.CreateDelegate(typeof(Func<A, int>));
// 调用方法
var value = func(a);
Console.WriteLine(value);
public class A
{
private int _value = 10;
}
在上面的代码中,我们通过 Emit 创建了一个新的方法,这个方法可以直接访问类 A 的私有成员 _value。这种方法的性能比反射好很多,但是代码比较复杂,不易于维护。
Expression
Expression 是 .NET 提供的一种表达式树的技术。通过 Expression,我们可以创建一个表达式树,然后编译这个表达式树,生成一个可以访问私有成员的方法。以下是一个使用 Expression 访问私有成员的例子:
var a = new A();
// 创建一个表达式树,访问私有成员 _value
var parameter = Expression.Parameter(typeof(A), "x");
// 访问私有成员 _value
var field = Expression.Field(parameter, typeof(A).GetField("_value", BindingFlags.NonPublic | BindingFlags.Instance));
// 编译表达式树,生成一个可以访问私有成员 _value 的方法
var lambda = Expression.Lambda<Func<A, int>>(field, parameter);
// 调用方法
var func = lambda.Compile();
var value = func(a);
Console.WriteLine(value);
public class A
{
private int _value = 10;
}
在上面的代码中,我们通过 Expression 创建了一个表达式树,然后编译这个表达式树,生成了一个可以访问类 A 的私有成员 _value 的方法。这种方法的性能比反射好,代码也相对简单,但是仍然比直接访问复杂。
.NET8的解决方案
我想很多聪明的小伙伴都已经猜到了,在.NET8中新增的UnsafeAccessor就是用来访问私有成员的,我们可以通过UnsafeAccessor来访问私有成员,下面我们来看一下如何使用UnsafeAccessor来访问私有成员。
私有字段
using System.Runtime.CompilerServices;
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_value")]
static extern ref int GetValue(A a);
var a = new A();
var value = GetValue(a);
Console.WriteLine(value);
public class A
{
private int _value = 10;
}
首先我们需要引入System.Runtime.CompilerServices这个命名空间,然后定义一个staic extern ref方法,这个方法的返回值类型是字段的类型,然后它的参数就是对应实例的类型。在方法上面我们需要添加一个UnsafeAccessor特性,这个特性有一个参数UnsafeAccessorKind,这个参数表示我们要访问的是什么类型的私有成员,比如字段、属性、方法等等,这里我们要访问的是字段,所以我们传入的是UnsafeAccessorKind.Field,然后我们还需要指定要访问的字段的名称,这里我们要访问的是_value字段,所以我们传入的是Name = "_value",这样我们就可以通过UnsafeAccessor来访问私有成员了。
来看一下运行的结果,可以看到和我们预期的一样输出了10。

因为它是返回了ref的引用,所以我们可以通过这个引用来修改私有成员的值,比如我们修改一下_value的值,如下所示:
using System.Runtime.CompilerServices;
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_value")]
static extern ref int ValueAccessor(A a);
var a = new A();
ref var value = ref ValueAccessor(a);
Console.WriteLine(value);
value = 20;
Console.WriteLine(ValueAccessor(a));
public class A
{
private int _value = 10;
}
来看一下运行的结果,可以看到我们修改了_value的值,第二次输出的时候就变成了20。

私有构造方法
同样的,使用UnsafeAccessor我们也可以访问类中的私有构造方法和私有的方法,我们可以看到UnsafeAccessor有一个参数UnsafeAccessorKind,这个参数表示我们要访问的是什么类型的私有成员,比如字段、属性、方法等等,下方是它的定义:
private enum UnsafeAccessorKind
{
Constructor,
Method,
StaticMethod,
Field,
StaticField
};
先来看一下如何访问私有构造方法,如下所示:
using System.Runtime.CompilerServices;
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
static extern A CreateA(int value);
var a = CreateA(10);
Console.WriteLine(a.Value);
public class A
{
public readonly int Value;
private A(int value)
{
Value = value;
}
}
在上面的代码中,我们通过UnsafeAccessor访问了类A的私有构造方法,这个私有构造方法的参数是int类型的,我们可以看到我们通过UnsafeAccessor访问了私有构造方法,然后创建了一个A的实例,然后输出了Value的值,可以看到输出的结果是10,这样我们就可以通过UnsafeAccessor来访问私有构造方法了。

私有属性
由于属性是语法糖,编译器会自动为我们生成一个get和set方法,比如public int Value {get; set;},就会自动生成一个get_Value和set_Value方法,我这里就不单独对访问私有方法进行演示,直接演示访问私有属性,它和访问私有方法是一样的,如下所示:
using System.Runtime.CompilerServices;
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_Value")]
static extern int GetValue(A a);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_Value")]
static extern void SetValue(A a, int value);
var a = new A();
SetValue(a, 10);
Console.WriteLine(GetValue(a));
public class A
{
public int Value { get; set; }
}
在上面的代码中,我们通过UnsafeAccessor访问了类A的私有属性Value,这个私有属性有get和set方法,我们通过UnsafeAccessor访问了get和set方法,然后我们就可以访问私有属性了,这样我们就可以通过UnsafeAccessor来访问私有属性了。

局限性
当然,现在使用UnsafeAccessor还有一些局限性,大家在使用的过程中需要注意一下。
通用泛型
比如现阶段它不支持通用泛型,像下面这样的代码是不支持的:
[UnsafeAccessor(UnsafeAccessorKind.Field, Name="_myList")]
static extern ref List<T> Field<T>(MyClass<T> _this);
但是现在可以写成下方这样的代码:
[UnsafeAccessor(UnsafeAccessorKind.Field, Name="_myList")]
static extern ref List<string> StringField(MyClass<string> _this);
[UnsafeAccessor(UnsafeAccessorKind.Field, Name="_myList")]
static extern ref List<double> DoubleField(MyClass<double> _this);
不过这会在.NET9中解决,有兴趣的小伙伴可以关注下方的链接:
https://github.com/dotnet/runtime/issues/89439
私有类型
比如有下面这样的一个示例代码:
// Assembly A
private class C
{
private static int Method(int a) { ... }
}
// Assembly B
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name="Method")]
static extern int CallMethod(??? c, int a);
这里的问题是,我们不知道如何定义CallMethod的第一个参数,因为C是私有的,我们无法在CallMethod的入参中定义它。
静态类
比如有下面这样的一个示例代码:
// Assembly A
public static class C
{
private static int Method(int a) { ... }
}
// Assembly B
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name="Method")]
static extern int CallMethod(??? c, int a);
这里的问题是,我们无法在B中定义CallMethod的第一个参数,因为C是静态的,我们无法在CallMethod的入参中定义它。
私有类参数
比如有下面这样的一个示例代码:
// Assembly A
public class C
{
private class D { }
private static int Method(D d) { ... }
}
// Assembly B
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name="Method")]
static extern int CallMethod(??? d); // Unable express D type as a parameter.
这里的问题是,我们无法在B中定义CallMethod的入参,因为D是私有的,我们无法在CallMethod的入参中定义它。包括目前还有私有返回值参数也是无法定义的。
但是这些问题在.NET9中也会解决,有兴趣的小伙伴可以关注下方的链接:
https://github.com/dotnet/runtime/issues/90081
总结
在本文中,首先介绍了在.NET8之前访问私有成员的几种方法,包括反射、Emit、和Expression。这些方法虽然可以实现访问私有成员,但是各有其优缺点,例如反射性能较差,Emit和Expression代码复杂度较高。
随后,我们详细介绍了.NET8新增的特性UnsafeAccessor,这是一种更方便、更高效的访问私有成员的方法。我们通过实例代码演示了如何使用UnsafeAccessor访问私有成员,包括私有字段、私有构造方法和私有属性。并且,UnsafeAccessor还支持修改私有成员的值。
然而,UnsafeAccessor目前还存在一些局限性,例如不支持通用泛型,无法访问私有类型、静态类和私有类参数。但是,这些问题预计在.NET9中将得到解决。
总的来说,UnsafeAccessor为.NET开发者提供了一个新的工具,使我们能够更方便、更高效地访问私有成员。虽然当前还存在一些局限性,但随着.NET的不断发展和进步,我们有理由相信这些问题将会得到解决。同时,我们也期待.NET在未来能够提供更多的功能和特性,以满足我们日益增长的开发需求。
.NET性能优化交流群
相信大家在开发中经常会遇到一些性能问题,苦于没有有效的工具去发现性能瓶颈,或者是发现瓶颈以后不知道该如何优化。之前一直有读者朋友询问有没有技术交流群,但是由于各种原因一直都没创建,现在很高兴的在这里宣布,我创建了一个专门交流.NET性能优化经验的群组,主题包括但不限于:
- 如何找到.NET性能瓶颈,如使用APM、dotnet tools等工具
- .NET框架底层原理的实现,如垃圾回收器、JIT等等
- 如何编写高性能的.NET代码,哪些地方存在性能陷阱
希望能有更多志同道合朋友加入,分享一些工作中遇到的.NET性能问题和宝贵的性能分析优化经验。目前一群已满,现在开放二群。
如果提示已经达到200人,可以加我微信,我拉你进群: ls1075
另外也创建了QQ群,群号: 687779078,欢迎大家加入。
抽奖送书活动预热!!!
感谢大家对我公众号的支持与陪伴!为庆祝公众号一周年,抽奖送出一些书籍,请大家关注公众号后续推文!

【.NET8】访问私有成员新姿势UnsafeAccessor(上)的更多相关文章
- C#中访问私有成员
首先访问一个类的私有成员不是什么好做法.大家都知道私有成员在外部是不能被访问的.一个类中会存在很多私有成员:如私有字段.私有属性.私有方法.对于私有成员造访,可以套用下面这种非常好的方式去解决. pr ...
- C#中访问私有成员技巧
源代码是别人的,你就不能修改源代码,只提供给你dll.或者你去维护别人的代码,源代码却有丢失.这样的情况如果你想知道私有成员的值,甚至去想直接调用类里面的私有方法.那怎么办呢?其实在.net中访问私有 ...
- C#中访问私有成员--反射
首先我必须承认访问一个类的私有成员不是什么好做法.大家也都知道私有成员在外部是不能被访问的.而一个类中会存在很多私有成员:如私有字段.私有属性.私有方法.对于私有成员访问,可以套用下面这种非常好的方式 ...
- VC6.0中友元函数无法访问类私有成员的解决办法
举个例子: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #inclu ...
- 使用C#反射机制访问类的私有成员【转】
首先我必须承认访问一个类的私有成员不是什么好做法.大家也都知道私有成员在外部是不能被访问的.而一个类中会存在很多私有成员:如私有字段.私有属性.私有方法.对于私有成员访问,可以套用下面这种非常好的方式 ...
- CPP-基础:关于私有成员的访问
a.C++的类的成员函数中,允许直接访问该类的对象的私有成员变量. b.在类的成员函数中可以访问同类型实例的私有变量. c.拷贝构造函数里,可以直接访问另外一个同类对象(引用)的私有成员. d.类的成 ...
- java中用反射访问私有方法和私有成员[转]
转自: http://zhouyangchenrui.iteye.com/blog/470521 java的反射可以绕过访问权限,访问到类的私有方法和成员.可能这点会引起安全性的讨论.反射的使用帮助解 ...
- [C++参考]私有成员变量的理解
私有成员变量的概念,在脑海中的现象是,以private关键字声明,是类的实现部分,不对外公开,不能在对象外部访问对象的私有成员变量. 然而,在实现拷贝构造函数和赋值符函数时,在函数里利用对象直接访问了 ...
- 面向对象~~类的成员: 私有成员,公有成员, 实例方法, 类方法, 静态方法, 属性(property), isinstance ,issubclass, 元类(type)
一 私有成员公有成员 公有成员: 在任何地方都能访问 私有成员: 只有在类的内部才能访问 类从加载时,只要遇到类中的私有成员,都会在私有成员前面加上_类名 二 实例方法 实例方法就是类的实例能够使用的 ...
- JS 的私有成员为什么钦定了 #?
翻译自 tc39/proposal-class-fields 译者按:社区一直以来有一个声音,就是反对使用 # 声明私有成员.但是很多质疑的声音过于浅薄.人云亦云.其实 TC39 早就对此类呼声做过回 ...
随机推荐
- 杭电多校第二场 DOS Card
杭电多校第二场 DOS Card 评价一下这道题:我写过最爽的线段树题. 这道题真的非常令人身心愉悦,非常厉害的一道线段树入门题.我写这个一次调试都没有,过了样例就交了就过了,一切都是行云流水. 这道 ...
- Active Directory Basic
Active Directory 是 Windows 域网络的目录服务 介绍 Active Directory 是在域内部连接的机器和服务器的集合,它们是构成 Active Directory 网络的 ...
- Java中打印对象输出的字符串到底是什么?
前言 我们在进行 Java 编程时,经常要打印对象,有的是查看是否拿到了该对象,有的是查看该对象中的数据.打印输出的却是一知半解的字符串,那么这个字符串是怎么来的?代表什么?我们如何打印出对象中的数据 ...
- 【Python&RS】GDAL对栅格数据重采样/对齐栅格
GDAL(Geospatial Data Abstraction Library)是一个在X/MIT许可协议下的开源栅格空间数据转换库.它利用抽象数据模型来表达所支持的各种文件格式.它 ...
- Android Studio 引入kotlin 协程
首先保证创建了kotlin项目,然后,两个步骤: 1. 加入dependency,在 build.gradle(:app)中,加入 implementation 'org.jetbrains.kotl ...
- Nanoframework 操作单片机蓝牙配置WIFI的案例
Nanoframework 操作单片机蓝牙配置WIFI的案例 通过Nanoframework的蓝牙配置Wifi的名称和密码 下面是基本需要的工具 ESP32设备一个 需要支持蓝牙和wifi,一般情况的 ...
- 基于Microsoft SEAL 同态加密场景特性
基于Microsoft SEAL 同态加密场景特性 同态加密是一种特殊的加密技术,它允许在加密状态下进行计算操作而无需解密数据.在传统的加密算法中,对加密的数据进行运算操作通常需要先解密数据,然后再进 ...
- Vue项目学习
一.二维数组尝试 var vm = new Vue({ el: "#app", data: { huilv:[ [6.8540, 132.9787, 1298.7013, 1.32 ...
- 牛客题解-mixup2混乱的奶牛(状压dp)
题解-mixup2混乱的奶牛 [原题连接](1026-mixup2混乱的奶牛_2021秋季算法入门班第八章习题:动态规划2 (nowcoder.com)) 题目描述 混乱的奶牛 [Don Piele, ...
- 深度解析SpringBoot内嵌Web容器
你好,我是刘牌! 前言 今天分享一个SpringBoot的内嵌Web容器,在SpringBoot还没有出现时,我们使用Java开发了Web项目,需要将其部署到Tomcat下面,需要配置很多xml文件, ...