Pull to refresh
235
0
Сергей Тепляков @SergeyT

Пользователь

Send message
Из плюсов: довольно приличное количество мыслей для размышлений, разбитых по разделам. Начинаешь читать главы по Equals/GetHashCode, любые несогласия с автором вынуждают к определенному ресерчу, который позволяет понять, где же на самом деле правда. И таких «вбросов» достаточно много; на часть из них сам автор дает вменяемые ответы, а вот ответы на другие вбросы придется искать в другом месте.

Возможно недостатки подпортили впечатление сильнее, в результате чего положительные моменты не так сильно отпечатались в памяти. Все будет зависеть от того, что вы читали до этого. Если для вас в книге Скита не много нового, то эту книгу нет смысла читать. Если же у вас пара лет опыта работы с платформой .NET и языком C#, то в книге Вагнера будет много полезного.
double.NaN хитрый зверь:

double.NaN.Equals(double.NaN) — true
double.NaN == double.NaN — false

Просто если бы не работал Equals, то вы бы не смогли из словаря double.NaN достать.
Речь немного не об этом. Речь о выделенном, что при создании экземпляра вначале вызывается статический конструктор базового класса, а потом статический конструктор наследника. В некоторых случаях (при наличии атрибута beforefieldinit у типа), при создании экземпляра наследника даже вызов статического конструктора наследника не гарантируется (подробности здесь).

Но даже в базовом случае, порядок вызова статических конструкторов противоположен порядку вызова экземплярных конструкторов: экземплярные конструкторы взываются от базового к наследнику, а статические конструкторы — от наследника к базовому.

Вот пруф:

internal class Base
{
    public Base()
    {
        Console.WriteLine("Base.ctor");
    }

    static Base()
    {
        Console.WriteLine("Base.cctor");
    }
}
    
internal class Derived : Base
{
    public Derived()
    {
        Console.WriteLine("Derived.ctor");
    }

    static Derived()
    {
        Console.WriteLine("Derived.cctor");
    }
}


При вызове new Derived() на экране мы получим следующее:

Derived.cctor Base.cctor Base.ctor Derived.ctor
В данном случае a — это выражение с побочными эффектами. Конечно же, два последовательных вызова метода могут возвращать разный результат.

Речь же идет о том, есть ли тип, желательно в BCL, для которого рефлексивность оператора == не выполняется.
Спасибо за добавление контекста, в этом случае неточность становится более четкой:) Дело в том, что в .NET-е, рефлексивность обязан обеспечивать метод Equals, но ее нет у оператора ==.
Это одна из причин (помимо того, что оператор == не полиморфный), почему для определения эквивалентности объектов в том же Dictionary используется именно Equals, а не оператор ==.

И Double.NaN является известным примером, когда Equals остается рефлексивным, а оператор == — нет.
Я же не пишу о возврате в С++, но многие мои коллеги, пришедшие из С++ изменяют свое отношение к структурам в .NET-е, после понимания количества проблем, которые могут с ними произойти.
Кстати, на на конструктор копирования структуры я даже не знаю, как с хаками повлиять. Разве что через CLR Profiling API, но даже в этом я не очень уверен.
Я так и не понял, какая связь между конструктором по умолчанию и процессом копирования?

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

При копировании не должен вызываться конструктор по умолчанию, этого не происходит в том же С++, и этого не происходит в .NET при бинарной или DataContract десериализации объекта. Это нормальное и ожидаемое поведение.

Еще раз: к каким логическим проблемам приведет наличие явного конструктора без параметров в контексте передачи структур по значению и почему его можно создать в IL-е?
В С++, например, есть четкое разделение между конструктором компирования и конструктором по умолчанию и никаких логических проблем при этом нет.

Здесь есть такое же разделение, но при этом конструктор копирования «генерируется» не компилятором, а CLR. Так что я не вижу никакой связи между отсутствием конструкторов по умолчанию в языке C# и передачей по значению с автоматическим копированием всех полей структуры.
Более того, this в конструкторе структуры по сути является out параметром, если «дефолтный» конструктор не вызывается, и является ref-параметром, если «дефолтный» конструктор вызывается:

struct SomeStruct
{
      private int _i;
      private double _d;

    public SomeStruct(int i)
       : this()
    {
        // this в теле метода является ref SomeStruct
        CreateOrUpdate(ref this);
    }

    public SomeStruct(double d)
    {
       // this() не вызывается, значит this является out SomeStruct:
       Create(out this);
    }

    public static void CreateOrUpdate(ref SomeStruct str)
    {
        str = new SomeStruct();
    }

    public static void Create(out SomeStruct str)
    {
        str = new SomeStruct();
    }
}
Там почти в самом конце примеры кода с комментариями, когда будет вызываться настоящий конструктор по умолчанию, а когда нет:

// Не вызывется
var cvt = default(CustomValueType);
Во-первых, вот небольшая линка, подтверждающая мое мнение о сходстве между методом Dispose в .NET стеке и деструктором.

Во-вторых, в С++ сам объект сможет разрушить это состояние лишь в том случае, если его будут правильно использовать. Создайте объект в куче и уже никто и ничего разрушать не будет. Так что деструктор и метод Dispose оба будут работать правильно только в том случае, если вызывающий код работает правильно. В этом вопросе их поведение аналогично, просто в первом случае (в С++) вам нужна локальная переменная, а во-втором случае (в C#) — нужно обрамление объекта в блок using.

Ну, и в третьих, а как же С++/CLI? Есть ли там RAII или нет? Ведь там вместо деструктора вызывается метод Dispose?
Тут, кажись, все сложнее.
Если следовать некоторому здравому смыслу, то в некоторых случаях (например, при работе с енуменраторами коллекций из BCL) боксинга быть не должно, поскольку это бы убило весь смысл от использования структуры в качестве енумератора.

С другой стороны, если подходить к этому делу формально, то все становится немного сложнее. Компиллер языка C# преобразует using для не-nullable значимых типов в следующую конструкцию:

{    
    ResourceType resource = expression;         
    try {
             statement;
         }
     finally 
     {
         ((IDisposable)resource).Dispose();  
     }
 }


А такой код (с вызовом метода Dispose через приведение к интерфейсу приводит к генерации IL-инструкции constraint, которая ведет себя очень хитро: упаковки не будет, если структура реализует метод интерфейса обычным образом и будет, если метод интерфейса реализован явно
У меня конструктор locker-а захватывает ресурс (блокировку) и освобождает его в методе Dispose, который вызывается автоматически при выходе из области видимости. Что семантически аналогично автоматическому вызову деструктора и используется, нужно сказать, для этих же целей (формально, вызов метода Dispose разрушает инвариант объекта и дальнейшее использование такого объекта приводит к неопределенному поведению). В С++ такое случается реже, но тоже не исключено, если мы, вдруг, где-то сохраним ссылку на объект, деструктор которого будет вызван.

Если следовать вашей логике, то на C++/CLI RAII тоже невозможен, а ведь там на уровне языка зашит вызов метод Dispose, вместо вызова финализатора, совсем аналогично тому, как действует конструкция using.
Ну так try-with-resources и блок using — это как раз и есть представление идиомы RAII в Java (начиная с 7-й версии) и в C#, соответственно. Без этих же конструкций, идиома RAII делается «вручную» с помощью блока finally, просто в этом случае мы делаем все руками.

З.Ы. В .NET гарантии вызова финализатора точно такие же, как и в Java, т.е. никаких.
Нет, финализатором в языке C# я назвал конструкцию, синтаксически идентичную деструктору языка С++:

class SomeCppClass {
  public:
    SomeCppClass() {} // constructor
    ~SomeCppClass() {} // destructor, calls automatically
};


Финализатор — это не блок finally, это «деструктор» вызываемый сборщиком мусора.

class SomeCSharpClass {
  publc SomeCSharpClass() {} // constructor
  ~SomeCSharpClass() {} // finalizer, calls by GC
}
А какое имеет отношение финализатор к блоку finally?
Насколько я помню, RSDN Magazine тоже входит в перечень ВАК-овских изданий.
Достаточно погулять по Джерси или НЮ, чтобы понять какова доступность iPhone-а для жителей штатов. Там айфон — это не удел распальцованных ребят, это обычная железка для большинства населения. Совершенно нормальная ситуация, когда у камрада бомжеватого (с нашей точки зрения вида) будет кобура с айфоном.
Достаточно запустить исходный пример, чтобы увидеть, что это не так.

Information

Rating
Does not participate
Location
Washington, США
Registered
Activity