Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >实例方法和静态方法有区别吗?

实例方法和静态方法有区别吗?

作者头像
蒋金楠
发布于 2023-07-10 01:33:22
发布于 2023-07-10 01:33:22
28900
代码可运行
举报
文章被收录于专栏:大内老A大内老A
运行总次数:0
代码可运行

实例方法和静态方法有区别吗?对于很多人来说,这是一个愚蠢的问题。因为我们都知道它们的区别,实例方法作用于某个具体的上下文对象,该上下文对象可以利用this关键字获得;静态方法则是定义在某个类型中,不存在上下文对象的概念。但是如果我们从函数的角度来看的话,不论是静态方法还是实例方法都是一个用于处理输入参数的操作,貌似又没有什么区别。

以如下这个用于封装一个整数的IntValue类型为例,它具有两个AsInt32方法,实例方法返回当前InValue对象的_value字段;静态方法将IntValue对象作为参数,返回该对象的_value字段。我们的问题是:这两个AsInt32方法有分别吗?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var target = new IntValue(123);
target.AsInt32();
IntValue.AsInt32(target);

public class IntValue
{
    private readonly int _value;
    public IntValue(int value) => _value = value;

    public int AsInt32() => _value;
    public static int AsInt32(IntValue value) => value._value;
}

我们从IL的视角来看这两个方法的声明和实现。如下面的代码片段所示,从方法声明来看,实例方法AsInt32和静态方法AsInt32确实不同,但是它们的实现却完全一致。方法涉及三个IL指令:ldarg.0提取第1个参数压入栈中,具体入栈的是指向IntValue对象的地址;目标IntValue对象的_value字段通过ldfld指令被加载,最终通过ret指令作为结果返回。实例方法也好,静态方法也罢,它们都被视为的普通函数。函数只有输入和输出,并不存在所谓的上下文对象(this)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.method public hidebysig
	instance int32 AsInt32 () cil managed
{
	// Method begins at RVA 0x2178
	// Header size: 1
	// Code size: 7 (0x7)
	.maxstack 8

	// return _value;
	IL_0000: ldarg.0
	IL_0001: ldfld int32 IntValue::_value
	IL_0006: ret
} // end of method IntValue::AsInt32
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.method public hidebysig static
	int32 AsInt32 (
		class IntValue 'value'
	) cil managed
{
	.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
		01 00 01 00 00
	)
	// Method begins at RVA 0x2180
	// Header size: 1
	// Code size: 7 (0x7)
	.maxstack 8

	// return value._value;
	IL_0000: ldarg.0
	IL_0001: ldfld int32 IntValue::_value
	IL_0006: ret
} // end of method IntValue::AsInt32

实例方法实际上将目标对象作为它的第一个参数,这与显式将目标对象作为第一个参数的静态方法并没有本质的区别,所以调用它们的IL代码也一样。如下所示的就是上面C#针对这两个方法的调用转换生成的IL代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.method private hidebysig static
	void '<Main>$' (
		string[] args
	) cil managed
{
	// Method begins at RVA 0x213c
	// Header size: 12
	// Code size: 23 (0x17)
	.maxstack 1
	.entrypoint
	.locals init (
		[0] class IntValue target
	)

	// IntValue intValue = new IntValue(123);
	IL_0000: ldc.i4.s 123
	IL_0002: newobj instance void IntValue::.ctor(int32)
	IL_0007: stloc.0
	// intValue.AsInt32();
	IL_0008: ldloc.0
	IL_0009: callvirt instance int32 IntValue::AsInt32()
	IL_000e: pop
	// IntValue.AsInt32(intValue);
	IL_000f: ldloc.0
	IL_0010: call int32 IntValue::AsInt32(class IntValue)
	IL_0015: pop
	// }
	IL_0016: ret
} // end of method Program::'<Main>$'

由于实例方法和静态方法的“无差异性”,我们可以使用一些Hijack的方式“篡改”现有某个类型的实例方法。比如我们在IntValue类型(可以定义任意类型中)中定义了一个总是返回int.MaxValue的AlwaysMaxValue方法。在演示程序中,我们通过调用Hijack方法将IntValue的实例方法AsInt32“替换”这个AlwaysMaxValue方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var target = new IntValue(123);
Hijack(()=>target.AsInt32(), () => IntValue.AlwaysMaxValue(null!));
Debug.Assert(target.AsInt32() == int.MaxValue);

public class IntValue
{
    private readonly int _value;
    public IntValue(int value) => _value = value;
    public int AsInt32() => _value;
    public static int AsInt32(IntValue value) => value._value;

    public static int AlwaysMaxValue(IntValue _) => int.MaxValue;
}

如下所示的就是这个Hijack方法的定义。它的两个方法表示调用原始方法和篡改方法的表达式,我们利用它们得到对应的MethodInfo对象。我们利用MethodHandle得到方法句柄,并进一步利用GetFunctionPointer方法得到具体的指针地址。有了这两个地址,我们就可以计算出它们之间的偏移量,然后利用Marshal.Copy方法“篡改”了原始方法的指令。具体来说,我们将原始方法的初始指令改为跳转指令JUMP,通过设置的偏移量跳转到新的方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void Hijack(Expression<Action> originalCall, Expression<Action> targetCall)
{
    var originalMethod = ((MethodCallExpression)originalCall.Body).Method;
    var targetMethod = ((MethodCallExpression)targetCall.Body).Method;

    RuntimeHelpers.PrepareMethod(originalMethod.MethodHandle);
    RuntimeHelpers.PrepareMethod(targetMethod.MethodHandle);

    var sourceAddress = originalMethod.MethodHandle.GetFunctionPointer();
    var targetAddress = (long)targetMethod.MethodHandle.GetFunctionPointer();

    int offset = (int)(targetAddress - (long)sourceAddress - 5);

    byte[] instruction = {
        0xE9, // JUMP
        (byte)(offset & 0xFF),
        (byte)((offset >> 8) & 0xFF),
        (byte)((offset >> 16) & 0xFF),
        (byte)((offset >> 24) & 0xFF)
    };

    Marshal.Copy(instruction, 0, sourceAddress, instruction.Length);
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-07-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
30分钟?不需要,轻松读懂IL
先说说学IL有什么用,有人可能觉得这玩意平常写代码又用不上,学了有个卵用。到底有没有卵用呢,暂且也不说什么学了可以看看一些语法糖的实现,或对.net理解更深一点这些虚头巴脑的东西。最重要的理由就是一个
用户1147588
2018/01/04
8890
30分钟?不需要,轻松读懂IL
关于Expression Tree和IL Emit的所谓的"性能差别"
昨天写了《三种属性操作性能比较》,有个网友写信问我一个问题:从性能上看,Expression Tree和IL Emit孰优孰劣?虽然我在回信中作了简单的回答,但不知道这个网友是否懂我的意思。反正今天呆
蒋金楠
2018/02/08
9490
[C#3] 1-扩展方法
1.从DEMO开始 先看一个扩展方法的例子: 1 class Program 2 { 3 public static void Main() 4 { 5 Int32 myNum = 1; 6 myNum = myNum.AddToOldNum(1); 7 Console.WriteLine(myNum); 8 } 9 } 10 11 public static clas
blackheart
2018/01/19
6810
C#扩展方法原理及其使用
今天群里一个小伙伴问了这样一个问题,扩展方法与实例方法的执行顺序是什么样子的,谁先谁后(这个问题会在文章结尾回答)。所以写了这边文章,力图从原理角度解释扩展方法及其使用。
AI.NET 极客圈
2019/07/19
1.7K0
C#扩展方法原理及其使用
『.Net反射』ILGenerator.Emit 动态MSIL 编程
--------------------------------------------------------------------------
yaphetsfang
2020/07/30
4390
[C#6] 5-自动属性增强
0. 目录 C#6 新增特性目录 1. 老版本代码 1 internal class Person 2 { 3 public string Name { get; private set; } 4 public int Age { get; private set; } 5 6 public Person(string name,int age) 7 { 8 Name = name; 9 Age = age; 10
blackheart
2018/01/19
6450
[C#6] 5-自动属性增强
进阶篇:以IL为剑,直指async/await
接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理。 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅
用户1147588
2018/01/04
1.3K0
进阶篇:以IL为剑,直指async/await
深入理解C# 3.x的新特性(2):Extension Method[下篇]
四、Extension Method的本质 通过上面一节的介绍,我们知道了在C#中如何去定义一个Extension Method:它是定义在一个Static class中的、第一个Parameter标记为this关键字的Static Method。在这一节中,我们来进一步认识Extension Method。 和C# 3.0的其他新特性相似,Extension Method仅仅是C#这种.NET Programming Language的新特性而已。我们知道,C#是一种典型的编译型的语言,我们编写的Sour
蒋金楠
2018/02/07
7300
C# 的事件,一般你不需要担心它的线程安全问题!
时不时会有小伙伴跟我提到在 C# 写事件 += -= 以及 Invoke 时可能遇到线程安全问题。然而实际上这些操作并不会有线程安全问题,所以我特别写一篇博客来说明一下,从原理层面说说为什么不会有线程安全问题。
walterlv
2023/10/22
4630
老生常谈:值类型 V.S. 引用类型
我在面试的时候经常会问一个问题:“谈谈值类型和引用的区别”。对于这个问题,绝大部分人都只会给我两个简洁的答案:“值类型分配在栈中,引用类型分配在堆中”,“在默认情况下,值类型参数传值(拷贝),引用类型参数传引用”。其实这个问题有很大的发挥空间,如果能够从内存布局、GC、互操作、跨AppDomain传递等方面展开,相信会加分不少。这篇文章独辟蹊径,从“变量”的角度讨论值类型和引用类型的区别。
蒋金楠
2023/07/10
3180
老生常谈:值类型 V.S. 引用类型
IL DASM反编译工具使用c# https://www.cnblogs.com/caokai520/p/4921706.html
使用C#的猿人或多或少都会对微软的IL反编译工具(ildasm.exe)有所认识。我最早接触到这工具是公司同事使用他反编译exe程序,进行研读和修改。感觉他还是很强大。   IL是微软平台上的一门中间语言,我们常写的C#代码在编译器中都会自动转换成IL,然后在由即时编译器(JIT Compiler)转化机器码,最后被CPU执行。ildasm.exe反编译工具将IL汇编成可跨平台可执行的(pe)文件。可供我们了解别人代码和修改。有了他我们看待问题可以不用停留在编辑器层面,可深入中间层。
vv彭
2021/01/06
2.8K0
IL DASM反编译工具使用c#    https://www.cnblogs.com/caokai520/p/4921706.html
[C#6] 4-string 插值
0. 目录 C#6 新增特性目录 1. 老版本的代码 1 internal class Person 2 { 3 public string Name { get; set; } 4 public int Age { get; set; } 5 6 public override string ToString() 7 { 8 return string.Format("[name={0},age={1}]", Name, Age); 9
blackheart
2018/01/19
5960
C# 泛型约束 new() 你必须要知道的事
本文要讲的是关于泛型约束无参构造函数 new 的一些底层细节和注意事项。写这篇文章的原因也是因为看到 github 上,以及其他地方看到的代码都是那么写的,而我一查相关的资料,发现鲜有人提到这方面的细节,所以才有了此文。
梁规晓
2020/11/05
2K0
[C#2] 2-匿名方法
1.匿名方法应用和机制 先看一段代码(C#1.0): 1 //这里加了参数<为了说明delegate关键字后面的参数列表> 2 public delegate void MyDelegate(object sender, EventArgs e); 3 class Program 4 { 5 static void Main() 6 { 7 new Program().function(); 8 } 9 private void functi
blackheart
2018/01/19
5940
[C#2] 2-匿名方法
理解C#泛型运作原理
我们都知道泛型在C#的重要性,泛型是OOP语言中三大特征的多态的最重要的体现,几乎泛型撑起了整个.NET框架,在讲泛型之前,我们可以抛出一个问题,我们现在需要一个可扩容的数组类,且满足所有类型,不管是值类型还是引用类型,那么在没有用泛型方法实现,如何实现?
ryzenWzd
2021/03/07
7260
C# 给多线程传参的三种方式
从《C#高级编程》了解到给线程传递参数有两种方式,一种方式是使用带ParameterizedThreadStart委托参数的Thread构造函数,另一种方式是创建一个自定义类,把线程的方法定义为实例的方法,这样就可以初始化实例的数据,之后启动线程。
vv彭
2021/02/02
3.3K0
MSIL学习------从HelloWorld开始
  前段时间突然想搞搞IL语言,于是在博客园中找到了包建强前辈关于IL的文章学习,并且在包前辈博客里看到了09年他与赵劼前辈关于是否有必要学习IL语言的争论,作为一个刚入此行业的新人,没有站在那个高度不敢去评论什么,并且我的引路教员在知道我学IL时就跟我说学习IL还不如学习汇编,IL语言就是一堆指令,谁背的多谁就越精通,我那个教员说的也不错,IL语言就是一堆指令,或许就是站的角度不同,我教员他不止局限于.NET,对C++和汇编都有一定研究,但是现在我还是只局限于.NET体系,学好.NET我感觉对于CIL和CLR一定得有一定的了解。所以我个人的观点是在.NET平台干活的人还是有必要学习学习IL的。现在IL我只是局限于刚学习阶段,所以想写下博客来记录我的学习记录
莫问今朝
2018/08/31
5980
MSIL学习------从HelloWorld开始
《你必须知道的.NET》读书笔记:从Hello World认识IL
  IL是.NET框架中间语言(Intermediate Language)的缩写。使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL(Intermediate Language)的代码。
Edison Zhou
2018/08/20
5020
《你必须知道的.NET》读书笔记:从Hello World认识IL
【.Net底层剖析】3.用IL来理解属性
概述: 我们经常在code中用到属性,但是我们真的知道属性和字段的区别吗?为什么会有属性这个用法?带着这两个问题,我们来用IL中间语言剖析一下属性(Property) C#中如何定义一个属性 publ
悟空聊架构
2018/05/18
8830
[C#6] 1-using static
0. 目录 C#6 新增特性目录 1. 老版本的代码 1 using System; 2 3 namespace csharp6 4 { 5 internal class Program 6 { 7 private static void Main(string[] args) 8 { 9 Console.WriteLine("blackheart"); 10 } 11 } 12 } 上面这段
blackheart
2018/01/19
8760
[C#6] 1-using static
相关推荐
30分钟?不需要,轻松读懂IL
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验