8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

从实现结构体中调用 C# 接口默认方法(无需装箱)

Drifter64 1月前

38 0

我唯一能想到的如下,远非理想:interface IBar { void Foo() => Console.WriteLine(\'Hello from interface!\');}struct Baz : IBar { // 编译器错误...

我唯一能想到的如下,远非理想:

interface IBar {
    void Foo() => Console.WriteLine("Hello from interface!");
}

struct Baz : IBar {
    // compiler error
    void Test1() => this.Foo();

    // IIRC this will box
    void Test2() => ((IBar)this).Foo();

    // this shouldn't box but is pretty complicated just to call a method
    void Test3() {
        impl(ref this);

        void impl<T>(ref T self) where T : IBar
            => self.Foo();  
    }
}

有没有更直接的方法来做到这一点?

(相关以及我如何得到这个问题: 从实现类调用 C# 接口默认方法 )

帖子版权声明 1、本帖标题:从实现结构体中调用 C# 接口默认方法(无需装箱)
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Drifter64在本站《c#》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 如果我们不依赖公认答案指出的 JIT 去虚拟化魔法,那么 唯一的 “官方方法”就是您猜测的选项 3:

    // this shouldn't box but is pretty complicated just to call a method
    void Test3() {
        impl(ref this);
    
        void impl<T>(ref T self) where T : IBar
            => self.Foo();  
    }
    

    我们依靠 constrained callvirt IL 来帮助我们。

    但它仍然与.NET 6 和 7 的基准测试代码相冲突:

    // identical method bodies for all methods below 
    interface I {
        int DefaultOne() {
            var result = 0;
            for (int i = 0; i < 100; i++) {
                var a = i % 2;
                var b = a * 4;
                result += b;
            }
            return result;
        }
    
        int DefaultTwo() {
            var result = 0;
            for (int i = 0; i < 100; i++) {
                var a = i % 2;
                var b = a * 4;
                result += b;
            }
            return result;
        }
    }
    
    struct S : I {
        public int DefaultTwo() {
            var result = 0;
            for (int i = 0; i < 100; i++) {
                var a = i % 2;
                var b = a * 4;
                result += b;
            }
            return result;
        }
    }
    

    通用约束和基准测试方法:

    int BenchmarkDefaultOne() {
        S str = default;
        var result = DefaultOneGeneric(ref str);
    
        return result;
    }
    
    int BenchmarkDefaultTwo() {
        S str = default;
        var result = DefaultTwoGeneric(ref str);
    
        return result;
    }
    
    
    int DefaultOneGeneric<T>(ref T tparam) where T : I {
        var result = 0;
        for (int i = 0; i < 100; i++) {
            result += tparam.DefaultOne();
        }
    
        return result;
    }
    
    int DefaultTwoGeneric<T>(ref T tparam) where T : I {
        var result = 0;
        for (int i = 0; i < 100; i++) {
            result += tparam.DefaultTwo();
        }
        return result;
    }
    

    结果(仅限 AllocatedBytes)

    BenchmarkDefaultOne  2,400
    BenchmarkDefaultTwo  0
    

    约束 constrained 方式(没有 JIT 帮助),这是可以预料到的

    如果 thisType 是值类型并且 thisType 没有实现方法, 那么 ptr 将被 取消引用 , 装箱 ,并作为 'this' 指针传递给 callvirt 方法指令。

    因此, 无法保证 通过我们的结构调用默认接口方法(未实现的方法)。

  • 我不能代表反对者发言,但我猜他们至少发现了你的答案中的两个错误:1)你的答案是推测性的,而问题的作者应该得到真正的答案,肯定能解决他们的问题,2)在我看来,你的答案很可能最终会限制 Baz 值,这正是 OP 试图避免的。

  • 我没有投反对票,但是你继承自一个无效的非接口(除非它是在 C# 8.0 中)。

  • 我并不反对投反对票,但是投反对票的人,请帮助我了解问题所在,以便我可以吸取教训。

  • 我还没有为 c# 8.0 设置,所以我不确定这是否可行,但这里有一个你可以尝试的想法:

    struct Baz : IBar
    {
        public void CallFoo()
        {
            this.AsBar().Foo();
        }
    
        public IBar AsBar()
        {
            return this;
        }
    }
    
  • orp 1月前 0 只看Ta
    引用 7

    如上所述,jit 可以省略某些框,前提是框的创建和使用对 jit 都是可见的,并且框在 IL 中没有重复……在核心中,异步状态机核心逻辑现在取决于此。其中一些选项已恢复到完整框架,例如 4.8 也应该能够做到这一点。我们仍然可以做很多事情来改进这里……

  • @Velocirobtor .NET Core 和 .NET ... Old 有很大不同。链接的问题表明,自 Core 2.1 以来,显式接口实现得到了优化。DIM 依赖于 Core 2.0 运行时功能,我怀疑其中许多都涉及优化。

  • 感谢您找到答案。如果 DIM 的行为与隐式接口实现相同,那么关于重复的说法可能是正确的。

  • 引用 10

    太棒了,我不知道 .NET core 做了那些优化。实际上我手边没有 C#8 就绪的编译器,所以我用隐式实现和 .NET 框架对其进行了测试。遗憾的是,那个没有优化装箱。我必须稍微尝试一下,看看优化是否得到保证。

  • 我认为没有任何分配。 这个 可能重复的问题 答案 解释了 JIT 编译器可以在许多情况下避免装箱,包括显式接口实现调用。JIT 团队的 Andy Ayers 在评论中验证了这一点,并提供了 实现此功能的 PR 链接

    我修改了该答案中的代码:

    using System;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Running;
    
    namespace DIMtest
    {
        interface IBar {
            int Foo(int i) => i;
        }
    
        struct Baz : IBar {
            //Does this box?        
            public int  Test2(int i) => ((IBar)this).Foo(i);
        }
    
    
        [MemoryDiagnoser, CoreJob,MarkdownExporter]
        public class Program
        {
            public static void Main() => BenchmarkRunner.Run<Program>();
    
            [Benchmark]
            public int ViaDIMCast()
            {
                int sum = 0;
                for (int i = 0; i < 1000; i++)
                {
                    sum += (new Baz().Test2(i));
                }
    
                return sum;
            }
        }
    
    }
    

    结果没有显示任何分配:

    
    BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18956
    Intel Core i7-3770 CPU 3.40GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
    .NET Core SDK=3.0.100-preview9-014004
      [Host] : .NET Core 3.0.0-preview9-19423-09 (CoreCLR 4.700.19.42102, CoreFX 4.700.19.42104), 64bit RyuJIT
      Core   : .NET Core 3.0.0-preview9-19423-09 (CoreCLR 4.700.19.42102, CoreFX 4.700.19.42104), 64bit RyuJIT
    
    Job=Core  Runtime=Core  
    
    |     Method |     Mean |    Error |   StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
    |----------- |---------:|---------:|---------:|------:|------:|------:|----------:|
    | ViaDIMCast | 618.5 ns | 12.05 ns | 13.40 ns |     - |     - |     - |         - |
    

    我将返回类型更改为 int,就像链接的答案一样,以确保该方法不会被优化

  • @GSerg 啊,好吧,真不幸。我曾希望(滥用)使用 DIM 作为结构缺失继承的替代方案,但如果涉及这么多开销,我想我会放弃它。无论如何,谢谢你的回答。

返回
作者最近主题: