Почему IsNan является статическим методом в классе Double вместо свойства экземпляра?

Вопрос в заголовке, почему:

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

Вместо

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

Я спрашиваю, потому что при реализации настраиваемых структур, которые имеют особое значение с тем же значением, что и NaN, я предпочитаю второй.

Кроме того, производительность свойства обычно лучше, так как не позволяет копировать структуру в стеке, чтобы вызвать статический метод IsNaN (и поскольку мое свойство не является виртуальным, нет риска автоматического бокса). Конечно, это не проблема для встроенных типов, так как JIT может оптимизировать это легко.

Мое лучшее предположение на данный момент состоит в том, что, поскольку вы не можете иметь как свойство, так и статический метод с тем же именем в двойном классе, они предпочитают синтаксис, вдохновленный java. (На самом деле вы могли бы иметь как одно свойство get_IsNaN, так и другое статическое средство IsNaN, но оно будет запутывать на любом языке .Net, поддерживая синтаксис свойства)

Ответ 1

Интересный вопрос; не знаю ответа, но если он действительно вас обманет, вы можете объявить метод расширения, но он все равно будет использовать стек и т.д.

static bool IsNaN(this double value)
{
    return double.IsNaN(value);
}

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

Было бы лучше (для синтаксиса), если бы у С# были свойства расширения, но приведенное выше - это самое близкое, которое вы можете получить на данный момент, но оно должно быть "inline" вполне удовлетворительным.


Обновление; думая об этом, есть еще одна разница между статикой и экземпляром; С# всегда вызывает методы экземпляра с "callvirt", а не "call", даже если тип type запечатан недействительным. Так что, возможно, есть преимущество в производительности от статичности? К счастью, методы расширения по-прежнему считаются статическими, поэтому вы можете сохранить это поведение.

Ответ 2

Статические методы являются потокобезопасными, методы на примитивах обычно должны быть потокобезопасными для поддержки потоковой обработки на платформе (что означает, по крайней мере, безопасное из условий внутренней расы), методы экземпляра принимают управляемый указатель на структуру, что означает, что структура/примитив может быть изменен одновременно во время выполнения метода, с другой стороны, статические методы берут копию структуры/примитива и, следовательно, не зависят от условий расстановки потоков.

Если структура предназначена для обеспечения потокобезопасности, то методы должны быть созданы методом экземпляра только в том случае, если они выполняют атомарные операции, иначе должны быть выбраны статические методы.

(В качестве другого варианта можно использовать методы экземпляра, использующие блокировку, но они дороже, чем копирование)

Изменить: @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);
        }
    }
}

Метод экземпляра экрана печатает: 0 5

хотя структура неизменна. Для методов, защищенных потоками, используются статические методы.

Ответ 3

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

Обратите внимание, что это поведение не может наблюдаться с двойными значениями на общем процессоре, так как большинство инструкций x86 имеют версию, использующую куски с 64 битами, такие как movl.

Таким образом, безопасность потоков не является веской причиной того, что IsNaN является статическим:

  • Структура должна быть агностической для платформы, поэтому она не должна предполагать такие вещи, как архитектура процессора. Безопасность потока IsNaN зависит от того, что значения 64 бит всегда доступны и изменены атомарно в целевой архитектуре (а Compact framework - не x86...).
  • IsNaN бесполезен сам по себе и в контексте, когда несколько потоков могут получить доступ к someVar, этот код в любом случае небезопасен (независимо от безопасности потока IsNaN):
 
print("code sample");
if (!double.IsNaN(someVar))
    Console.WriteLine(someVar);

Я имею в виду, что даже если IsNaN реализуется путем сравнения == со всеми возможными значениями NaN... (на самом деле невозможно) ... которые заботятся о том, чтобы ценность развивалась во время выполнения метода, если в любом случае он мог бы измениться после завершения метода... или он может даже быть промежуточным значением, которое никогда не должно было быть здесь, если целевая архитектура не является x86...

Доступ к внутренним значениям в двух разных потоках является НЕ безопасным в целом, поэтому я не вижу заинтересованности в обеспечении некоторой иллюзии безопасности, ставя любой статический метод при работе с структурами или любым другим типом,

Ответ 4

Различие между экземпляром и static является основополагающим моментом, который язык С# (и Java, как вы говорите) выбирает для пояснения (в С++ вы можете вызвать метод static через экземпляр, но thats просто синтаксис - под экземпляром hood.StaticX - это тот же самый экземпляр высказывания Class.StaticX.

Переход к свободным интерфейсам начал распутывать многое из этого, хотя...

Ответ 5

Я помню слова наставника, молясь, чтобы любой метод, который не использует никаких других переменных, кроме параметров, является статическими методами.

Я действительно не знаю, почему, и не думал о позади, но логически, это кажется хорошим. Заинтересованы в ответе: -)

Ответ 6

Я думаю, что Марк был на ответ.

Проблема заключается в том, когда вам нужно вызвать методы экземпляра в методе valuetypes, значение вставляется в квадрат. Это может привести к серьезному снижению производительности.

Ответ 7

Double.IsNan следует тому же шаблону, что и String.IsNullorEmpty. Последнее ведет себя так, как это происходит, потому что, к сожалению, нет способа объявить, что метод не виртуального экземпляра должен использоваться с нулевым значением "this". Хотя такое поведение может быть нечетным для изменяемых типов ссылок, оно было бы очень полезно для вещей, которые должны быть ссылочными типами, но которые должны вести себя семантически как неизменные значения. Например, тип "String" был бы намного более удобным, если бы вызов его свойств на нулевом объекте мог бы вести себя одинаково, чтобы вызвать их на пустой строке. Как бы то ни было, существует нечетный mish-mosh контекстов, в которых объект с нулевой строкой будет рассматриваться как пустая строка, а те, в которых пытается использовать один, порождают ошибку. Было бы намного чище, если бы строка вела себя последовательно, как тип значения, который инициализируется пустой строкой.