代码之家  ›  专栏  ›  技术社区  ›  Atriace

方法的数据类型

  •  4
  • Atriace  · 技术社区  · 7 年前

    委托如何存储对函数的引用?源代码似乎将其称为对象,而它调用方法的方式似乎是源代码中的删节。有人能解释一下C是怎么处理的吗?


    void foo() { ... }
    

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

    Function foo = function():void { ... };
    

    在我的经验中,匿名函数通常是不好的形式,但在这里,它在整个语言标准中都很丰富。因为你不能用它的数据类型定义一个函数(显然隐含/处理是由编译器承担的), 如何存储对方法的引用 如果从未声明类型?

    我正努力避免 Delegates 以及它的变种( Action & Func ),都是因为。。。

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

    在看 source code for the 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);
    

    如果方法确实是一个对象,这并不能解释它是如何被调用的。感觉这根本不是代码,实际调用的代码是从源代码库中编辑的。


    对以前评论的答复

    @艾米: 在那句话之后我马上举了一个例子来解释我的意思。如果函数以数据类型作为前缀,则可以编写真正的匿名函数,并将其作为属性存储到对象中,例如:

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

    从目前的情况来看,C#不允许您编写真正的匿名函数,并将该函数隐藏在后面 Delegates Lambda expressions .

    @500内部服务器错误: 我已经解释了我想做什么。我甚至大胆地说。你以为这里有什么不可告人的动机;我只是想理解C#如何存储对方法的引用。我甚至提供了到源代码的链接,以便其他人可以自己阅读代码并帮助回答问题。

    显然,如果我已经在Google上找到了典型的答案,那么唯一能找到我要找的答案的地方就是这里。我意识到这超出了大多数C开发人员的知识范围,因此我提供了源代码链接。如果你不知道答案,你不必回答。

    5 回复  |  直到 7 年前
        1
  •  6
  •   dymanoid    7 年前

    首先,在C#中没有这样的“函数”。C#中的每个可执行实体实际上都是一个方法,也就是说,它属于 class . 即使您定义lambda或匿名函数如下:

    collection.Where(item => item > 0);
    

    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();
        }
    }
    

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

    IL代码将包含一个类 Example 编译器生成 类,该类包含的方法的主体是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()
    

    注意这些 call callvirt

    NET运行时包含JIT(实时)编译器。它将IL代码转换为实际可执行的代码 .

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

    这一切都将发生在 呼叫 卡尔维特

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

    这个 DynamicInvoke 委托的方法做同样的事情:它指示运行时调用一个方法(在一些额外的参数检查等之后)。你提到的“死胡同” RuntimeMethodHandle.InvokeMethod 是直接对运行时的内在调用。此方法的参数为:

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

    总之,方法不表示为 object s在C#(当然你可以 delegate 对象 方法,它 对它)。

    不能在C#中的类之外定义全局“函数”。你呢 能够 获取指向已编译(jitted)方法代码的直接本机指针,甚至可能通过直接操作自己进程的内存来手动调用它。但为什么呢?

        2
  •  3
  •   Nerlog    7 年前

    显然,你误解了脚本语言、C/C++和C语言之间的主要区别。

    我想主要的困难在于 在C语言中没有函数这样的东西# . 完全。 C#7引入了新特性“本地函数”,但这不是JS中的函数。 方法 这个名字故意不同于 功能 程序 强调C#中所有可执行代码都属于一个类的事实。

    编译器将在具有匿名方法声明的方法所属的同一(或嵌套)类中生成一个实方法。

    这个 simple article

    所以呢 所有方法(匿名或非匿名)都属于一个类。 除了说 .

    根据你所说的引用,它可以是

    1. MethodInfo 类,用于引用方法的反射信息,
    2. RuntimeMethodHandle
    3. 代表,即 从一个内存指针,但逻辑上可以用来“传递一个方法引用到另一个方法”。

    方法信息 MethodInfo.Invoke 非常相似的方法 Function..apply JS中的函数。您已经在委托源代码中看到了如何使用该类。

    希望它能澄清一些事情。

        3
  •  1
  •   Strom    7 年前

    委托只是指向具有指定参数和返回类型的方法的指针(要跳转到的内存位置)。任何与签名(参数和返回类型)匹配的方法都有资格完成角色,而与定义的对象无关。Anonymous只是表示委托未命名。

    C#是一种强类型语言。这意味着每个表达式(包括委托)都必须具有返回类型(包括void)和强类型参数(如果有的话)。泛型的创建是为了允许显式类型在一般上下文(如列表)中使用。

    换句话说,委托是C++安全回调的类型安全管理版本。

    委托允许代码跳转到正确的处理程序而不测试任何条件,从而有助于消除switch语句。

    委托类似于Javascript术语中的闭包。

    如果您希望传递函数,我建议您尝试F#。

    编辑

    如果您试图模仿javascit的行为,我将尝试通过接口使用继承。我可以模拟多重继承,同时也是类型安全的。但是,请注意,它不能完全取代Javascript的依赖注入模型。

        4
  •  0
  •   George Vovos    7 年前

    正如您可能发现的,C#没有JavaScript示例中的函数概念。


    Javascript是一种动态语言,这就是为什么你可以做你所描述的事情

    (不执行此操作,请使用Func/Action)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    
    namespace ConsoleApp1
    {
        class Program
        {
            private static Dictionary<string, Func<int, int, int>> FuncOps = new Dictionary<string, Func<int, int, int>>
            {
                {"add", (a, b) => a + b},
                {"subtract", (a, b) => a - b}
            };
    
            //There are no anonymous delegates
            //private static Dictionary<string, delegate> DelecateOps = new Dictionary<string, delegate>
            //{
            //    {"add", delegate {} }
            //};
    
            private static Dictionary<string, dynamic> DynamicOps = new Dictionary<string, dynamic>
            {
                {"add", new Func<int, int, int>((a, b) => a + b)},
                {"subtract", new Func<int, int, int>((a, b) => a - b)},
                {"inverse", new Func<int,  int>((a) => -a )} //Can't do this with Func
            };
    
            private static Dictionary<string, MethodInfo> ReflectionOps = new Dictionary<string, MethodInfo>
            {
                {"abs", typeof(Math).GetMethods().Single(m => m.Name == "Abs" && m.ReturnParameter.ParameterType == typeof(int))}
            };
    
            static void Main(string[] args)
            {
    
                Console.WriteLine(FuncOps["add"](3, 2));//5
                Console.WriteLine(FuncOps["subtract"](3, 2));//1
    
                Console.WriteLine(DynamicOps["add"](3, 2));//5
                Console.WriteLine(DynamicOps["subtract"](3, 2));//1
                Console.WriteLine(DynamicOps["inverse"](3));//-3
    
                Console.WriteLine(ReflectionOps["abs"].Invoke(null, new object[] { -1 }));//1
                Console.ReadLine();
            }
        }
    }
    

    delegate object CustomFunc(params object[] paramaters);
    private static Dictionary<string, CustomFunc> CustomParamsOps = new Dictionary<string, CustomFunc>
    {
        {"add", parameters => (int) parameters[0] + (int) parameters[1]},
        {"subtract", parameters => (int) parameters[0] - (int) parameters[1]},
        {"inverse", parameters => -((int) parameters[0])}
    };
    
    Console.WriteLine(CustomParamsOps["add"](3, 2)); //5
    Console.WriteLine(CustomParamsOps["subtract"](3, 2)); //1
    Console.WriteLine(CustomParamsOps["inverse"](3)); //-3
    
        5
  •  0
  •   Lockethot    7 年前

    来自维基百科:

    反射是计算机程序检查,

    因此,当C#编译并找到一个委托时,它只需要生成一个函数,然后在变量中存储一个对该函数的反射引用,这样就可以传递它,并用它做各种很酷的事情。实际上,并不是将函数本身存储在变量中,而是存储一个引用,它有点像一个地址,指向函数存储在RAM中的位置。