代码之家  ›  专栏  ›  技术社区  ›  Julien Roncaglia

为什么isnan是double类上的静态方法而不是实例属性?

  •  12
  • Julien Roncaglia  · 技术社区  · 16 年前

    问题在标题中,为什么:

    return double.IsNaN(0.6d) && double.IsNaN(x);
    

    而不是

    return (0.6d).IsNaN && x.IsNaN;
    

    我会问,因为当实现具有与NaN相同含义的特殊值的自定义结构时,我倾向于选择第二个。

    此外,该属性的性能通常更好,因为它避免复制堆栈上的结构以调用isnan静态方法(而且由于我的属性不是虚拟的,因此没有自动装箱的风险)。当然,对于内置类型来说,这并不是真正的问题,因为JIT可以轻松地优化这个问题。

    我现在最好的猜测是,由于在双类中不能同时使用属性和静态方法,所以他们喜欢Java启发的语法。(实际上,您可以同时定义get-isan属性getter和isan静态方法,但在支持属性语法的任何.NET语言中,这都会令人困惑)

    7 回复  |  直到 13 年前
        1
  •  9
  •   Marc Gravell    16 年前

    有趣的问题;不知道答案-但是如果它真的让你感到厌烦,你可以声明一个扩展方法,但是它仍然会使用堆栈等。

    static bool IsNaN(this double value)
    {
        return double.IsNaN(value);
    }
    
    static void Main()
    {
        double x = 123.4;
        bool isNan = x.IsNaN();
    }
    

    如果C具有扩展属性(对于语法来说)会更好,但是上面的内容是目前最接近的,但是无论如何它应该“内联”的很好。


    更新;考虑到它,静态和实例之间还有另一个区别;c# 总是 使用“”调用实例方法 callvirt “而不是” call “,即使其他类型是密封的,也不能为空。那么,让它静态化可能会带来性能上的好处呢?幸运的是,扩展方法仍然是静态的,所以您可以保留这种行为。

        2
  •  14
  •   Jay Bazuzi Buck Hodges    16 年前

    静态方法是线程安全的,基元上的方法通常需要是线程安全的,以支持平台中的线程(意味着至少不受内部争用条件的影响),实例方法采用结构的托管指针,这意味着在方法执行时可以同时修改结构/基元,另一方面静态方法复制结构/原语,因此不受线程竞争条件的影响。

    如果结构是为了线程安全的,那么只有当方法执行原子操作时,才应将它们作为实例方法,否则应选择静态方法。

    (作为另一个选项,可以使用使用锁定的实例方法,但它们比复制成本高)

    编辑:@virtualbackfox我已经准备好并举例说明结构上的实例方法即使在不可变的结构上也不是线程安全的:

    using System;
    using System.Threading;
    
    namespace CA64213434234
    {
        class Program 
        {
            static void Main(string[] args)
            {
                ManualResetEvent ev = new ManualResetEvent(false);
                Foo bar = new Foo(0);
                Action a =  () => bar.Display(ev);
                IAsyncResult ar = a.BeginInvoke(null, null);
                ev.WaitOne();
                bar = new Foo(5);
                ar.AsyncWaitHandle.WaitOne();
            }
        }
    
        public struct Foo
        {
            private readonly int val;
            public Foo(int value)
            {
                val = value;
            }
            public void Display(ManualResetEvent ev)
            {
                Console.WriteLine(val);
                ev.Set();
                Thread.Sleep(2000);
                Console.WriteLine(val);
            }
        }
    }
    

    显示实例方法打印: 零 五

    即使结构是不变的。对于线程安全方法,使用静态方法。

        3
  •  4
  •   Julien Roncaglia    16 年前

    @波普·加泰林:我对你说的话不满意:

    如果结构是为了线程安全的,那么只有当方法执行原子操作时,才应将它们作为实例方法,否则应选择静态方法。

    下面是一个小程序,演示静态方法不能解决结构的这个问题:

    using System;
    using System.Threading;
    using System.Diagnostics;
    
    namespace ThreadTest
    {
        class Program
        {
            struct SmallMatrix
            {
                double m_a, m_b, m_c, m_d;
    
                public SmallMatrix(double x)
                {
                    m_a = x;
                    m_b = x;
                    m_c = x;
                    m_d = x;
                }
    
                public static bool SameValueEverywhere(SmallMatrix m)
                {
                    return (m.m_a == m.m_b)
                        && (m.m_a == m.m_c)
                        && (m.m_a == m.m_d);
                }
            }
    
            static SmallMatrix s_smallMatrix;
    
            static void Watcher()
            {
                while (true)
                    Debug.Assert(SmallMatrix.SameValueEverywhere(s_smallMatrix));
            }
    
            static void Main(string[] args)
            {
                (new Thread(Watcher)).Start();
                while (true)
                {
                    s_smallMatrix = new SmallMatrix(0);
                    s_smallMatrix = new SmallMatrix(1);
                }
            }
        }
    }
    

    注意,在公共处理器上使用双值无法观察到这种行为,因为大多数x86指令的版本都使用64位块,例如 movl .

    因此线程安全似乎不是Isnan保持静态的一个好理由:

    1. 框架应该是平台不可知的,因此它不应该预先假定处理器体系结构之类的东西。线程安全性取决于这样一个事实:在目标体系结构上,64位的值总是原子性地被访问和修改(而紧凑的框架目标不是x86…)。
    2. isnan本身在多个线程可以访问的上下文中是无用的 someVar 此代码无论如何都不安全(不管isnan的线程安全如何):
    print("code sample");
    if (!double.IsNaN(someVar))
        Console.WriteLine(someVar);
    

    我的意思是,即使是通过执行 == 与所有可能的NaN值进行比较…(不太可能) …谁在乎值在方法执行过程中发生变化,如果无论如何,一旦方法终止,它可能会发生变化…或者它甚至可能是一个中间值,如果目标体系结构不是x86,它就不应该出现在这里……

    访问两个不同线程中的固有值是 不是 一般来说是安全的,所以我认为在处理结构或任何其他类型时,通过将任何方法设置为静态的方式来提供某种安全感是没有兴趣的,

        4
  •  0
  •   Ruben Bartelink    16 年前

    实例与实例之间的区别 static 是C语言(和Java所说)选择清楚的一个基本点(在C++中,您可以调用 静止的 方法通过一个实例,但这只是语法-在hood实例下。staticx与instance class.staticx相同。

    朝着流畅界面的发展已经开始揭示很多这方面的问题…

        5
  •  0
  •   Kevin Vaughan    16 年前

    我记得一位导师的话,他祈祷任何不使用参数以外任何其他变量的方法都是静态方法。

    我真的不知道为什么,也没有考虑到背后的原因,但从逻辑上讲,这似乎很好。 不管怎样,对答案感兴趣;-)

        6
  •  0
  •   leppie    16 年前

    我想马克知道答案了。

    问题是,当需要对值类型调用实例方法时,该值被装箱。这将导致严重的性能损失。

        7
  •  0
  •   supercat    13 年前

    double.isnan遵循与string.isNullOrEmpty相同的模式。后者的行为与它的行为一样,因为遗憾的是,没有任何方法可以声明非虚拟实例方法应与空“this”一起使用。虽然对于可变的引用类型,这样的行为可能很奇怪,但对于必须是引用类型但行为在语义上应类似于不可变的值的情况,这将非常有用。例如,如果在空对象上调用“string”类型的属性的行为与在空字符串上调用它们的行为相同,那么“string”类型将更加方便。事实上,在很多上下文中,空字符串对象被视为空字符串,而那些试图使用空字符串的对象将产生错误。如果字符串作为一个初始化为空字符串的值类型一直保持一致的行为,则情况会更清楚。