界面强制转换与类强制转换

时间:2011-01-03 15:38:56

标签: c# .net interface casting

我一直认为,在某些情况下,铸造可能成为衡量表现的一个可衡量的障碍。当我们开始处理令人讨厌的异常投掷\捕获的不连贯网络时,情况可能会更多。

鉴于我希望在编程时创建更正确的启发式方法,我已经被提示向.NET专家提出这个问题:接口是否比类型转换更快?

举一个代码示例,假设存在:

public interface IEntity { IParent DaddyMommy { get; } }
public interface IParent : IEntity { }
public class Parent : Entity, IParent { }
public class Entity : IEntity
{
    public IParent DaddyMommy { get; protected set; }
    public IParent AdamEve_Interfaces
    {
        get
        {
            IEntity e = this;
            while (e.DaddyMommy != null)
                e = e.DaddyMommy as IEntity;
            return e as IParent;
        }   
    }
    public Parent AdamEve_Classes
    {
        get
        {
            Entity e = this;
            while (e.DaddyMommy != null)
                e = e.DaddyMommy as Entity;
            return e as Parent;
        }
    }
}

那么,AdamEve_Interfaces比AdamEve_Classes更快吗?如果是这样,多少钱?而且,如果你知道答案,为什么?

6 个答案:

答案 0 :(得分:17)

这里的一些答案提出了基准测试,这是朝着正确方向迈出的一步,但只是旅程的第一步。

我的团队在这方面做了大量的分析和基准测试。短版本是,在某些情况下,接口会产生较小但可衡量的性能成本。 然而实际成本取决于很多因素,包括支持多少接口,给定引用的接口数量,访问模式等等。 CLR具有很多启发式功能,旨在加快常见情况下的接口访问速度。

如果您正在对其中一种常见情况进行基准测试,但您的实际程序属于不太常见的情况,那么您的基准测试主动有害,因为它会为您提供误导性数据。

更好地对真实代码进行真实的性能测量。使用分析器,以两种方式编写代码,并查看这两种方式是否可测量,以可见且与用户相关的方式更快地重复。

至于你对投掷和捕捉的提及:投掷和捕捉的表现成本应该是无关紧要的。根据定义,例外是例外,而不是普通。此外,例外通常表明某些事情很快就会停止;通常情况下,某些事情是否会尽快停止并不重要。如果您遇到异常门控的情况,那么您需要解决更大的问题:停止抛出这么多异常。抛出的异常应该非常罕见。

答案 1 :(得分:7)

看看这里:

http://thatstoday.com/robbanp/blog/6/25/csharp-performance--cast-vs-interface

而且,是的,你似乎是对的。

编辑嗯,好像我错了。就像我的“patrício”Martinho Fernandes评论说的那样,上面的链接完全是假的(但我会把它保留在这里,为了诚实的编辑)。

我现在有空闲时间,所以我写了一个简单的性能测量代码:

public partial class Form1 : Form
{
    private const int Cycles = 10000000;

    public interface IMyInterface
    {
        int SameProperty { get; set; }
    }

    public class InterfacedClass : IMyInterface
    {
        public int SameProperty { get; set; }
    }

    public class SimpleClass
    {
        public int SameProperty { get; set; }
    }

    public struct InterfacedStruct : IMyInterface
    {
        public int SameProperty { get; set; }
    }

    public struct SimpleStruct
    {
        public int SameProperty { get; set; }
    }

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e) {
        var simpleClassTime = MeasureSimpleClass();
        var interfacedClassTime = MeasureInterfacedClass();
        var simpleStructTime = MeasureSimpleStruct();
        var interfacedStructTime = MeasureInterfacedStruct();

        var message = string.Format(
            "simpleClassTime = {0}\r\ninterfacedClassTime = {1}\r\nsimpleStructTime = {2}\r\ninterfacedStructTime = {3}",
            simpleClassTime,
            interfacedClassTime,
            simpleStructTime,
            interfacedStructTime
        );

        textBox.Text = message;
    }

    private static long MeasureSimpleClass() {
        var watch = Stopwatch.StartNew();
        var obj = new SimpleClass();

        for (var i = 0; i < Cycles; i++)
        {
            obj.SameProperty = i;
            var j = obj.SameProperty;
            obj.SameProperty = j;
        }

        return watch.ElapsedMilliseconds;
    }

    private static long MeasureInterfacedClass() {
        var watch = Stopwatch.StartNew();
        IMyInterface obj = new InterfacedClass();

        for (var i = 0; i < Cycles; i++) {
            obj.SameProperty = i;
            var j = obj.SameProperty;
            obj.SameProperty = j;
        }

        return watch.ElapsedMilliseconds;
    }

    private static long MeasureSimpleStruct()
    {
        var watch = Stopwatch.StartNew();
        var obj = new SimpleStruct();

        for (var i = 0; i < Cycles; i++)
        {
            obj.SameProperty = i;
            var j = obj.SameProperty;
            obj.SameProperty = j;
        }

        return watch.ElapsedMilliseconds;
    }

    private static long MeasureInterfacedStruct()
    {
        var watch = Stopwatch.StartNew();
        IMyInterface obj = new InterfacedStruct();

        for (var i = 0; i < Cycles; i++)
        {
            obj.SameProperty = i;
            var j = obj.SameProperty;
            obj.SameProperty = j;
        }

        return watch.ElapsedMilliseconds;
    }
}

结果是:

simpleClassTime = 274
interfacedClassTime = 339
simpleStructTime = 247
interfacedStructTime = 302

我之前认为class类型的界面会更快,而struct的界面会更慢(因为后者涉及装箱/拆箱),但事实并非如此:a看来具体的类/结构引用总是更快。

此外,它可能关注的对象:我认为性能是决定是否应该使用接口的良好标准。与其他人所说的不同,差异可以忽略不计。

答案 2 :(得分:7)

你必须衡量。

但是如果施法在你的代码中成为一个(潜在的)瓶颈,你就会在问题菜单上超越意大利面。

答案 3 :(得分:5)

您是否尝试过测试?这是一个运行10,000,000次的循环。在我的机器上,接口版本大约需要440毫秒,类版本大约需要410毫秒。非常接近,但整体而言,班级版本获胜。

using System;

namespace ConsoleApplication1
{
    public interface IEntity { IParent DaddyMommy { get; } }
    public interface IParent : IEntity { }
    public class Parent : Entity, IParent { }
    public class Entity : IEntity
    {
        public IParent DaddyMommy { get; protected set; }
        public IParent AdamEve_Interfaces
        {
            get
            {
                IEntity e = this;
                while (this.DaddyMommy != null)
                    e = e.DaddyMommy as IEntity;
                return e as IParent;
            }
        }
        public Parent AdamEve_Classes
        {
            get
            {
                Entity e = this;
                while (this.DaddyMommy != null)
                    e = e.DaddyMommy as Entity;
                return e as Parent;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Entity X = new Entity();
            Parent P;
            IParent IP;
            System.Diagnostics.Stopwatch ST = new System.Diagnostics.Stopwatch();
            Int32 i;

            ST.Start();
            for (i = 0; i < 10000000; i++)
            {
                IP = X.AdamEve_Interfaces;
            }
            ST.Stop();
            System.Diagnostics.Trace.WriteLine(ST.ElapsedMilliseconds);

            ST.Reset();

            ST.Start();
            for (i = 0; i < 10000000; i++)
            {
                P = X.AdamEve_Classes;
            }
            ST.Stop();
            System.Diagnostics.Trace.WriteLine(ST.ElapsedMilliseconds);
        }
    }

}

答案 4 :(得分:4)

假设没有定义静态转换运算符,强制转换应该大致相同。在类(而不是接口)上调用方法时,可能会有一些“内联”优化,但除非将方法调用 insane ,否则不会注意到这一点。

总而言之;两者都没有明显的性能问题。或者用另一种方式说:直到我描述并显示这一点,我先看看其他地方。

答案 5 :(得分:2)

首先,你不需要在这里进行转换,因为代码必须在没有强制转换的情况下工作。 IParentIEntity,所以它应该可以正常工作。

施法会对性能产生影响吗?稍微如果它涉及转换(如果类型实现IConvertible并且转换是必要的)。否则它可以忽略不计,因为所有它必须做的是进行类型检查,这应该是闪电般的。