C#'是'运营商的表现

Jub*_*Jub 90 c# clr performance gettype

我有一个需要快速性能的程序.在其内部循环之一中,我需要测试对象的类型以查看它是否继承自某个接口.

一种方法是使用CLR的内置类型检查功能.最优雅的方法可能是'is'关键字:

if (obj is ISpecialType)
Run Code Online (Sandbox Code Playgroud)

另一种方法是给基类我自己​​的虚拟GetType()函数,它返回一个预定义的枚举值(在我的情况下,实际上,我只需要一个bool).这种方法会很快,但不那么优雅.

我听说有一个专门针对'is'关键字的IL指令,但这并不意味着它在转换为本机程序集时执行速度很快.任何人都可以分享一些关于'是'与其他方法的表现的见解吗?

更新: 感谢所有明智的答案!似乎在答案中分散了几个有用的观点:安德鲁关于'是'自动执行演员表的观点是必不可少的,但Binary Worrier和Ian收集的表演数据也非常有用.如果编辑其中一个答案以包含所有这些信息,那就太棒了.

And*_*are 109

is如果在检查类型后,使用会损害性能,则会转换为该类型. is实际上将对象强制转换为您正在检查的类型,因此任何后续转换都是多余的.

无论如何你要投,这是一个更好的方法:

ISpecialType t = obj as ISpecialType;

if (t != null)
{
    // use t here
}
Run Code Online (Sandbox Code Playgroud)

  • @JubJub:不.失败的"as"基本上执行与`is`相同的操作(即,类型检查).唯一的区别是它然后返回`null`而不是`false`. (4认同)
  • 谢谢。但是,如果条件失败时我不打算强制转换对象,那么使用虚函数来测试类型是否会更好? (2认同)
  • 我只想指出,在过去几年中,我们获得了使用以下模式的能力:`if (obj is ISpecialType t) { t.DoThing(); }` (2认同)
  • Stackoverflow应该删除过时的答案,这会对后代产生误导。 (2认同)

Bin*_*ier 69

我和伊恩在一起,你可能不想这样做.

但是,正如您所知,两者之间的差异非常小,超过10,000,000次迭代

  • 枚举检查在700 毫秒(约)
  • IS检查是在 1000 毫秒(约)

我个人不会这样解决这个问题,但如果我被迫选择一种方法,它将是内置的IS检查,性能差异不值得考虑编码开销.

我的基础和派生类

class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
}

class MyClassA : MyBaseClass
{
    public MyClassA()
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
    }
}
class MyClassB : MyBaseClass
{
    public MyClassB()
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
    }
}
Run Code Online (Sandbox Code Playgroud)

JubJub:根据要求提供有关测试的更多信息.

我从控制台应用程序(调试版本)运行了两个测试,每个测试如下所示

static void IsTest()
{
    DateTime start = DateTime.Now;
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass a;
        if (i % 2 == 0)
            a = new MyClassA();
        else
            a = new MyClassB();
        bool b = a is MyClassB;
    }
    DateTime end = DateTime.Now;
    Console.WriteLine("Is test {0} miliseconds", (end - start).TotalMilliseconds);
}
Run Code Online (Sandbox Code Playgroud)

在发行版中运行时,我得到了60到70毫秒的差异,就像伊恩一样.

进一步更新- 2012年10月25日
之后一两年离开我发现了一些关于这一点,编译器可以选择忽略bool b = a is MyClassB在其释放,因为B不在任何地方使用.

这段代码...

public static void IsTest()
{
    long total = 0;
    var a = new MyClassA();
    var b = new MyClassB();
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass baseRef;
        if (i % 2 == 0)
            baseRef = a;//new MyClassA();
        else
            baseRef = b;// new MyClassB();
        //bool bo = baseRef is MyClassB;
        bool bo = baseRef.ClassType == MyBaseClass.ClassTypeEnum.B;
        if (bo) total += 1;
    }
    sw.Stop();
    Console.WriteLine("Is test {0} miliseconds {1}", sw.ElapsedMilliseconds, total);
}
Run Code Online (Sandbox Code Playgroud)

...始终显示is检查在大约57毫秒进入,并且枚举比较在29毫秒.

NB 我还是更喜欢is支票,差别太小而无法关心

  • +1实际测试性能,而不是假设. (33认同)
  • @Binary Worrier-你的**新**运算符分配类将完全掩盖'is'操作中的任何性能差异.为什么不通过重用两个不同的预分配实例来删除这些**新**操作,然后重新运行代码并发布结果. (10认同)
  • 使用Stopwatch类进行测试要好得多,而不是使用非常昂贵的DateTime.Now (3认同)
  • 我会考虑到这一点,但在这种情况下,我不认为这会影响结果.谢谢 :) (2认同)

Jar*_*rsk 22

好的,我正和某人聊聊这件事,并决定对此进行更多测试.据我所知,与测试您自己的成员或函数来存储类型信息相比,性能asis非常好.

我用过Stopwatch,我刚学到的可能不是最可靠的方法,所以我也试过了UtcNow.后来,我也尝试了处理器时间方法,这似乎UtcNow包括不可预测的创建时间.我也尝试使基类非抽象而没有虚拟,但它似乎没有显着的效果.

我在带有16GB RAM的Quad Q6600上运行它.即使进行了50万次迭代,数字仍然会在+/- 50左右的时间内反弹,因此我不会对微小的差异进行过多的阅读.

有趣的是,x64创建得更快但执行速度比x86慢

x64发布模式:
秒表:
As:561ms
Is:597ms
基本属性:539ms
基本字段:555ms
基本RO字段:552ms
虚拟GetEnumType()测试:556ms
虚拟IsB()测试:588ms
创建时间:10416ms

UtcNow:
As:499ms
Is:532ms
Base property:479ms
Base field:502ms
Base RO field:491ms
Virtual GetEnumType():502ms
Virtual bool IsB():522ms
创建时间:285ms(这个数字似乎与UtcNow不可靠.我也得到109ms和806ms.)

x86发布模式:
秒表:
As:391ms
Is:423ms
基本属性:369ms
基本字段:321ms
基本RO字段:339ms
虚拟GetEnumType()测试:361ms
虚拟IsB()测试:365ms
创建时间:14106ms

UtcNow:
As:348ms
Is:375ms
基本属性:329ms
基本字段:286ms
基本RO字段:309ms
虚拟GetEnumType():321ms
虚拟布尔IsB():332ms
创建时间:544ms(此数字似乎与UtcNow不可靠.)

这是大部分代码:

    static readonly int iterations = 50000000;
    void IsTest()
    {
        Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;
        MyBaseClass[] bases = new MyBaseClass[iterations];
        bool[] results1 = new bool[iterations];

        Stopwatch createTime = new Stopwatch();
        createTime.Start();
        DateTime createStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            if (i % 2 == 0) bases[i] = new MyClassA();
            else bases[i] = new MyClassB();
        }
        DateTime createStop = DateTime.UtcNow;
        createTime.Stop();


        Stopwatch isTimer = new Stopwatch();
        isTimer.Start();
        DateTime isStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] =  bases[i] is MyClassB;
        }
        DateTime isStop = DateTime.UtcNow; 
        isTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch asTimer = new Stopwatch();
        asTimer.Start();
        DateTime asStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i] as MyClassB != null;
        }
        DateTime asStop = DateTime.UtcNow; 
        asTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch baseMemberTime = new Stopwatch();
        baseMemberTime.Start();
        DateTime baseStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassType == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseStop = DateTime.UtcNow;
        baseMemberTime.Stop();
        CheckResults(ref  results1);

        Stopwatch baseFieldTime = new Stopwatch();
        baseFieldTime.Start();
        DateTime baseFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseFieldStop = DateTime.UtcNow;
        baseFieldTime.Stop();
        CheckResults(ref  results1);


        Stopwatch baseROFieldTime = new Stopwatch();
        baseROFieldTime.Start();
        DateTime baseROFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseROFieldStop = DateTime.UtcNow;
        baseROFieldTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethTime = new Stopwatch();
        virtMethTime.Start();
        DateTime virtStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].GetClassType() == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime virtStop = DateTime.UtcNow;
        virtMethTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethBoolTime = new Stopwatch();
        virtMethBoolTime.Start();
        DateTime virtBoolStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].IsB();
        }
        DateTime virtBoolStop = DateTime.UtcNow;
        virtMethBoolTime.Stop();
        CheckResults(ref  results1);


        asdf.Text +=
        "Stopwatch: " + Environment.NewLine 
          +   "As:  " + asTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           +"Is:  " + isTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           + "Base property:  " + baseMemberTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base field:  " + baseFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base RO field:  " + baseROFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType() test:  " + virtMethTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual IsB() test:  " + virtMethBoolTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Create Time :  " + createTime.ElapsedMilliseconds + "ms" + Environment.NewLine + Environment.NewLine+"UtcNow: " + Environment.NewLine + "As:  " + (asStop - asStart).Milliseconds + "ms" + Environment.NewLine + "Is:  " + (isStop - isStart).Milliseconds + "ms" + Environment.NewLine + "Base property:  " + (baseStop - baseStart).Milliseconds + "ms" + Environment.NewLine + "Base field:  " + (baseFieldStop - baseFieldStart).Milliseconds + "ms" + Environment.NewLine + "Base RO field:  " + (baseROFieldStop - baseROFieldStart).Milliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType():  " + (virtStop - virtStart).Milliseconds + "ms" + Environment.NewLine + "Virtual bool IsB():  " + (virtBoolStop - virtBoolStart).Milliseconds + "ms" + Environment.NewLine + "Create Time :  " + (createStop-createStart).Milliseconds + "ms" + Environment.NewLine;
    }
}

abstract class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
    public ClassTypeEnum ClassTypeField;
    public readonly ClassTypeEnum ClassTypeReadonlyField;
    public abstract ClassTypeEnum GetClassType();
    public abstract bool IsB();
    protected MyBaseClass(ClassTypeEnum kind)
    {
        ClassTypeReadonlyField = kind;
    }
}

class MyClassA : MyBaseClass
{
    public override bool IsB() { return false; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.A; }
    public MyClassA() : base(MyBaseClass.ClassTypeEnum.A)
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
        ClassTypeField = MyBaseClass.ClassTypeEnum.A;            
    }
}
class MyClassB : MyBaseClass
{
    public override bool IsB() { return true; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.B; }
    public MyClassB() : base(MyBaseClass.ClassTypeEnum.B)
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
        ClassTypeField = MyBaseClass.ClassTypeEnum.B;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • (有些奖金是上午5点 - 莎士比亚...)成为或不成为:这就是问题:在代码中是否更高尚受到抽象基础的枚举和属性,或者接受中间人的提议语言学家并通过调用它的指令,信任他们?猜测:想知道; 不再; 并且通过一个时间来辨别我们结束了时间限制的编码员继承人的头痛和一千个潜意识的奇迹."这是一个绝望的关闭.死,不,但要睡觉; 是的,我会睡觉,梦想是真实的,也可以从最基础的阶级中获得. (41认同)

Ian*_*Ian 16

安德鲁是对的.实际上,通过代码分析,Visual Studio将其报告为不必要的强制转换.

一个想法(不知道你在做什么在黑暗中是一个镜头),但我总是被建议避免这样检查,而是有另一个类.因此,不是根据类型进行一些检查和采取不同的操作,而是让课程知道如何处理自己...

例如,Obj可以是ISpecialType或IType;

他们都定义了DoStuff()方法.对于IType,它可以返回或执行自定义的东西,而ISpecialType可以做其他的东西.

然后,这将完全删除任何转换,使代码更清晰,更易于维护,并且类知道如何执行它自己的任务.


Kna*_*bax 13

我对两种类型比较的可能性进行了性能比较

  1. myobject.GetType()== typeof(MyClass)
  2. myobject是MyClass

结果是:使用"是"快约10倍!

输出:

类型比较的时间:00:00:00.456

时间比较:00:00:00.042

我的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication3
{
    class MyClass
    {
        double foo = 1.23;
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass myobj = new MyClass();
            int n = 10000000;

            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj.GetType() == typeof(MyClass);
            }

            sw.Stop();
            Console.WriteLine("Time for Type-Comparison: " + GetElapsedString(sw));

            sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj is MyClass;
            }

            sw.Stop();
            Console.WriteLine("Time for Is-Comparison: " + GetElapsedString(sw));
        }

        public static string GetElapsedString(Stopwatch sw)
        {
            TimeSpan ts = sw.Elapsed;
            return String.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Krz*_*cki 9

点Andrew Hare在执行is检查时失去了性能,然后执行了有效但在C#7.0中我们可以做的是检查女巫模式匹配以避免以后额外的演员:

if (obj is ISpecialType st)
{
   //st is in scope here and can be used
}
Run Code Online (Sandbox Code Playgroud)

如果您需要在多种类型之间进行检查,还可以进一步检查C#7.0模式匹配构造现在允许您switch对类型进行检查:

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在此处的文档中阅读有关C#中模式匹配的更多信息.

  • 当您使用无法控制的类型/类/接口时,@Dib OOP 理想就会被抛到窗外。当处理可以返回完全不同类型的多个值之一的函数的结果时,这种方法也很有用(因为 C# 仍然不支持联合类型 - 您可以使用像 `OneOf&lt;T.. .&gt;`但它们有重大缺点)。 (3认同)
  • SO 需要过滤按钮(在问题上)来获取适用于较新版本的框架、平台等的答案。该答案构成了 C# 7 正确答案的基础。 (2认同)

Gru*_*Gru 5

如果有人想知道,我已经在 Unity 引擎 2017.1 中进行了测试,在带有 i5-4200U CPU 的笔记本电脑上使用脚本运行时版本 .NET4.6(Experimantal)。结果:

Average Relative To Local Call LocalCall 117.33 1.00 is 241.67 2.06 Enum 139.33 1.19 VCall 294.33 2.51 GetType 276.00 2.35

全文:http : //www.ennoble-studios.com/tuts/unity-c-performance-comparison-is-vs-enum-vs-virtual-call.html