方法的数据类型

Atr*_*ace 4 c# methods types function

委托如何存储对函数的引用?源代码似乎将其称为Object,并且它调用方法的方式似乎从源代码中编辑.任何人都可以解释C#如何处理这个问题?


原帖

我似乎经常与C#对其程序员施加的抽象作斗争.一直困扰着我的是函数/方法的混淆.据我了解,所有方法实际上都是分配给类属性的匿名方法.这就是为什么没有函数以数据类型为前缀的原因.例如...

void foo() { ... }
Run Code Online (Sandbox Code Playgroud)

...将用Javascript编写为......

Function foo = function():void { ... };
Run Code Online (Sandbox Code Playgroud)

根据我的经验,匿名函数通常是不好的形式,但在这里它充满了整个语言标准.因为您无法使用其数据类型定义函数(并且显然编译器假定了隐含/处理),如果从未声明类型,如何存储对方法的引用

我正在努力避免Delegates及其变种(Action&Func),因为...

  1. 它是对实际发生的事物的另一种抽象
  2. 实例化这些类所需的不必要的开销(这些类又带有自己指向被调用方法的指针).

查看源代码Delegate.cs,它似乎简单地引用了函数的引用Object(参见第23-25行).


如果这些确实是对象,我们如何称呼它们?根据delegate.cs路径,它在以下路径上死路一条:

Delegate.cs:DynamicInvoke()> DynamicInvokeImpl()> methodinfo.cs:UnsafeInvoke()> UnsafeInvokeInternal()> RuntimeMethodHandle.InvokeMethod()>runtimehandles.cs:InvokeMethod()

internal extern static object InvokeMethod(object target, object[] arguments, Signature sig, bool constructor);
Run Code Online (Sandbox Code Playgroud)

这实际上并没有解释如果它确实是一个对象,它是如何调用的.感觉好像这根本不是代码,并且调用的实际代码已经从源代码库中编辑.

非常感谢您的帮助.


对先前评论的回应

@Amy:我在声明之后立即举了一个例子来解释我的意思.如果函数以数据类型为前缀,则可以编写一个真正的匿名函数,并将其作为属性存储到Object,例如:

private Dictionary<string, Function> ops = new Dictionary<string, Function> {
    {"foo", int (int a, int b) { return a + b } }
};
Run Code Online (Sandbox Code Playgroud)

既然这样,C#不允许你写真正的匿名功能,和墙壁该功能关闭落后DelegatesLambda expressions.

@ 500内部服务器错误:我已经解释了我想要做的事情.我甚至加粗了.你认为这里有任何别有用心的动机; 我只是想了解C#如何存储对方法的引用.我甚至提供了源代码的链接,以便其他人可以自己阅读代码并帮助回答问题.

@Dialecticus:显然,如果我已经在谷歌找到了典型的答案,那么找到我正在寻找答案的唯一其他地方就是在这里.我意识到这超出了大多数C#开发人员的知识,这就是我提供源代码链接的原因.如果您不知道答案,则无需回复.

dym*_*oid 6

虽然我不完全理解你对"真正的匿名函数","没有数据类型前缀"等的见解,但我可以解释一下用C#调用方法编写的应用程序.

首先,C#中没有这样的"功能".C#中的每个可执行实体实际上都是一种方法,也就是说,它属于一个class.即使您定义lambdas或匿名函数,如下所示:

collection.Where(item => item > 0);
Run Code Online (Sandbox Code Playgroud)

C#编译在后台创建一个编译器生成的类,并将lambda主体return item > 0放入编译器生成的方法中.

所以假设你有这个代码:

class Example
{
    public static void StaticMethod() { }
    public void InstanceMethod() { }
    public Action Property { get; } = () => { };
}

static class Program
{
    static void Main()
    {
        Example.StaticMethod();
        var ex = new Example();
        ex.InstanceMethod();
        ex.Property();
    }
}
Run Code Online (Sandbox Code Playgroud)

C#编译器将创建一个IL代码.IL代码不能立即执行,需要在虚拟机中运行.

IL代码将包含一个Example具有两个方法的类(实际上,四个 - 一个默认构造函数和属性getter方法将自动生成)和一个编译器生成的类,其中包含一个方法,该方法的主体是lambda表达式的主体.

IL代码Main将如下所示(简化):

call void Example::StaticMethod()
newobj instance void Example::.ctor()
callvirt instance void Example::InstanceMethod()
callvirt instance class [mscorlib]System.Action Example::get_Prop()
callvirt instance void [mscorlib]System.Action::Invoke()
Run Code Online (Sandbox Code Playgroud)

请注意那些callcallvirt说明:这些是方法调用.

要实际执行被调用的方法,需要将其IL代码编译为机器代码(CPU指令).这发生在名为.NET Runtime的虚拟机中.其中有几个像.NET Framework,.NET Core,Mono等.

.NET运行时包含JIT(即时)编译器.它在程序执行期间将IL代码转换为实际可执行代码.

当.NET运行时首次遇到IL代码" StaticMethod从类调用方法Example"时,它首先查看已编译方法的内部缓存.当没有匹配(这意味着这是该方法的第一次调用)时,运行时要求JIT编译器使用IL代码创建这样一个已编译并准备运行的方法.IL代码转换为一系列CPU操作并存储在进程的内存中.指向该编译代码的指针存储在缓存中以供将来重用.

这一切都将在IL callcallvirtIL指令背后发生(再次,简化).

一旦发生这种情况,Runtime就可以执行该方法了.CPU将编译代码的第一个操作地址作为下一个要执行的操作,并继续执行直到代码返回.然后,Runtime再次接管并继续下一个IL指令.

DynamicInvoke委托的方法做同样的事情:它指示运行时调用一个方法(在一些额外的参数检查之后).你提到的"死胡同" RuntimeMethodHandle.InvokeMethod是对Runtime的内在调用.该方法的参数是:

  • object target- 委托调用实例方法(this参数)的对象.
  • object[] arguments - 传递给方法的参数.
  • Signature sig- 要调用的实际方法Signature是一个内部类,它提供托管IL代码和本机可执行代码之间的连接.
  • bool constructor- true如果这是一个构造函数调用.

因此,在总结,方法并不表示为objectS IN C#(当你当然可以有一个delegate实例一个object,但它并不代表可执行方法,而提供了一个可调用参照它).

方法由Runtime调用,JIT编译器使方法可执行.

您无法在C#中的类之外定义全局"函数".你可以得到一个直接的本地指针编译(即时编译)方法的代码,甚至可能通过直接操作自己的过程的记忆手动调用它.但为什么?