为什么IsNan是Double类的静态方法而不是实例属性?

时间:2008-12-16 10:04:45

标签: c# oop

问题在标题中,为什么:

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

而不是

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

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

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

我现在最好的猜测是,因为你不能在double类中同时拥有属性和静态方法,所以他们更喜欢java启发的语法。 (事实上​​你可以同时定义一个get_IsNaN属性getter,另一个定义一个IsNaN静态方法,但是在支持属性语法的任何.Net语言中都会混淆)

7 个答案:

答案 0 :(得分:14)

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

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

(作为另一种选择,可以使用使用锁定的实例方法,但它们比复制更昂贵)

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

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

显示Instance方法打印: 0 5

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

答案 1 :(得分:9)

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

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 :(得分:4)

@Pop Catalin:我对你所说的不好:

  

如果结构是线程安全的,那么方法只有在进行原子操作时才应该成为实例方法,否则应该选择静态方法。

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

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

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

因此线程安全似乎不是IsNaN静态的好理由:

  1. 框架应该是平台无关的,所以它不应该以处理器架构之类的东西为前提。 IsNaN线程安全依赖于64位值总是在目标体系结构上原子访问和修改的事实(并且Compact框架目标不是x86 ......)。
  2. IsNaN本身无用,并且在多线程可以访问someVar的上下文中,此代码无论如何都是不安全的(无论IsNaN的线程安全性如何):
  3.  
    print("code sample");
    if (!double.IsNaN(someVar))
        Console.WriteLine(someVar);
    

    我的意思是即使IsNaN是通过==与所有可能的NaN值进行比较来实现的......(不太可能) ...如果在方法终止时它可能已经改变了,那么谁会关心在方法执行期间值的演变...或者它甚至可能是一个中间值,如果目标体系结构不是x86,它应该永远不会在这里...

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

答案 3 :(得分:0)

实例和static之间的区别是C#语言(和你说的Java)选择明确的基本点(在C ++中,你可以通过一个方法调用static方法。实例,但那只是语法 - 在引擎实例下.StaticX与实例Class.StaticX相同。

向流畅的界面迈进已经开始解决了很多这个问题......

答案 4 :(得分:0)

我记得导师的话,祈祷任何不使用参数的任何方法都是静态方法。

我真的不知道为什么,并没有想到后面,但从逻辑上讲,它似乎很好。 无论如何对答案感兴趣; - )

答案 5 :(得分:0)

我认为马克正在回答。

问题是当你需要在valuetypes上调用实例方法时,值被装箱。这会导致严重的性能损失。

答案 6 :(得分:0)

Double.IsNan遵循与String.IsNullorEmpty相同的模式。后者的行为与它的行为一样,因为遗憾的是,没有办法声明非虚拟实例方法应该与null“this”一起使用。虽然这种行为对于可变引用类型可能是奇怪的,但对于必须是引用类型但在语义上应该像不可变值一样的事物来说它将非常有用。例如,如果在空对象上调用其属性与在空字符串上调用它们的行为相同,则“String”类型会更方便。实际上,有一个奇怪的上下文,其中空字符串对象将被视为空字符串,而尝试使用其中的字符串将产生错误。如果字符串一直表现为一个初始化为空字符串的值类型,那就更清晰了。