Pull to refresh

Comments 66

Добавлю от себя пару любопытных заметок:
  1. Поля, помеченные как const, объявлены в классе, однако используются только компилятором — во все места их использования он подставляет фактические значения, не генерируя код обращения к полю. Это не оптимизация — среда CLR запрещает обращаться к константным полям так же, как к обычным.
  2. Если в метод передается анонимная функция вида SomeMethod(x => OtherMethod(x)), это можно переписать короче: SomeMethod(OtherMethod).
  3. string.Empty нельзя использовать, например, в качестве значения для параметра по умолчанию — а литерал пустой строки можно.
  4. Extension-методы можно в том числе вызывать как обычный статический метод, указывая имя класса и передавая объект явно в качестве первого параметра. Фактически они различаются только тем, что на методе и содержащем его классе ставится атрибут ExtensionAttribute, который подсказывает компилятору, что данный метод можно использовать как метод-расширение.
Ну это уже совсем очевидно, но на всякий случай поясню.
2. x => OtherMethod(x) это анонимная лямбда функция, причем в данном случае её сигнатура будет совпадать с методом OtherMetod (принимает такой же параметр x и возвращает результат OtherMetod). Т.к. кроме вызова OtherMethod она ничего не делает, мы просто выкидываем её, передавая вместо неё сам метод
3. Потому что параметр по умолчанию должен быть Compile Type Constant, а String.Empty это статическое поле класса.
4. Фактически это обычные статические методы статического класса, они не будут иметь каких-то привилегий в доступе к полям класса, в частности оттуда не не будут видны private и protected члены. Просто использование слова this добавляет «синтаксический сахар», позволяя вызывать их, записывая через точку от экземпляра, а не с помощью имени содержащего их класса.

5. ReSharper делает вашу жизнь проще, автоматически подчеркивая и исправляя ошибки, подобные описанной в пункте 2.
Пункт 3 также связан с пунктом 1: например, использовать int.MaxValue вполне можно. Почему string.Empty не сделали таким же константным полем, непонятно — ведь в MSIL есть инструкция для загрузки строки, аналогичная инструкции для загрузки числа.
Вот комментарий к данному полю.

// The Empty constant holds the empty string value.
//We need to call the String constructor so that the compiler doesn't mark this as a literal.
//Marking this as a literal would mean that it doesn't show up as a field which we can access from native.
public static readonly String Empty = "";

Это сделано для доступа к полю из неуправляемого(native) кода.
Вы серьезно были удивлены вас этим, особенно (1)?
Да, я думал что компилятор делает это исключительно ради оптимизации. Тот факт, что инструкция ldfld при обращении к такому полю кидает MissingFieldException, оказался для меня полной неожиданностью.
Автор конечно погреет свое самолюбие на почве того, как много людей оказались невежественней. Но все равно спасибо, я для себя действительно что-то открыл, чтение было полезным. На редкость.

От себя:
Методы расширения имеют небольшую особенность на фоне обычных методов — мы можем обращаться к методам, даже если объекты существуют как null, в то время как обычный метод в классе при таком выпадает. Проще говоря мы не можем создать рабочий метод в классе аля IsNull() { return a == null; }, в то время как метод расширения будет так работать. Например именно поэтому оно и работает
Это потому, что метод-расширение, преобразуется при компиляции в вызов соотв. статического метода из класса.
т.е. есть расширение
public static class Ext
{
public static bool Check(this object o)
{
return o == null;
}
}

Его вызов:

if(o.Check())
{
//do something
}

при компиляции преобразуется в

if(Ext.Check(o))
{
//do something
}
Почему вы пишите «Поля, помеченные как const»? Поля это поля. Константы это константы. У них разные реализации и назначение.
С точки зрения CLR «константа» — это не отдельная сущность, а поле с выставленным флагом IsLiteral.
Можете убедиться в этом с помощью Reflection:
typeof(int).GetField("MaxValue")
Угу, только главное тут не то как это выглядит с т.з. Reflection, а то что при обращении к public const в другой сборке. И поэтому это уже не поле.

Вы зацикливаетесь на CLR и MSIL забывая зачем эти сущности были введены.

Поля, помеченные как const, объявлены в классе, однако используются только компилятором — во все места их использования он подставляет фактические значения, не генерируя код обращения к полю.

Этот факт становится важным только при декомпиляции — использования константных полей пропадают из кода. В остальных случаях — бесполезная информация.

Если в метод передается анонимная функция вида SomeMethod(x => OtherMethod(x)), это можно переписать короче: SomeMethod(OtherMethod)
Extension-методы можно в том числе вызывать как обычный статический метод, указывая имя класса и передавая объект явно в качестве первого параметра.

На такие советы хочется сказать только одно: поставьте, наконец, решарпер.

string.Empty нельзя использовать, например, в качестве значения для параметра по умолчанию — а литерал пустой строки можно.

Не вижу смысла выделять ошибки компиляции в советы. Такие ошибки допускаются один раз, всегда обнаруживаются, тривиально исправляются. Та же претензия к некоторым пунктам статьи.
Мой комментарий и не претендовал на раскрытие сакральных знаний, которые облегчат жизнь всем пишущим на C#. Я просто перечислил несколько неочевидных для меня вещей в языке \ платформе, некоторые из которых действительно подсказал решарпер.
Автор оригинального топика (Damien Guard) всё-таки преследовал какие-то практические цели: научить полезному, помочь избежать ошибок. Зачем топик превращают в сборник забавных фактов о языке — мне не понять. В моём представлении подсказки, которые даются стандартными инструментами разработки, нет смысла выносить в топик. С таким же успехом можно пройтись по списку ошибок и предупреждений компилятора и инструментов статического анализа кода — советов наберутся тысячи.
Если в метод передается анонимная функция вида SomeMethod(x => OtherMethod(x)), это можно переписать короче: SomeMethod(OtherMethod).

Это можно назвать η-conversion.

Поля, помеченные как const, объявлены в классе, однако используются только компилятором

В частности, это значит, что если некоторая константа была выставлена наружу как const field, то её изменение требует перекомпиляции не только используемой сборки, но и использующей (т.е. ломающее изменение). Предоставление property такого недостатка лишено.

Фактически они различаются только тем, что на методе и содержащем его классе ставится атрибут ExtensionAttribute, который подсказывает компилятору, что данный метод можно использовать как метод-расширение.

Проще говоря, extension-методы — это не фича, это сахар.
Пункт 2: На самом деле не совсем так.
В случае вызова SomeMethod(OtherMethod) — будет всегда создаваться делегат.
В случае вызова SomeMethod(x => OtherMethod(x)) — делегат будет кешироваться.
С точностью до наоборот…
Очевидно, стоит или перечитать мой комментарий, или проверить самому. А если напишешь ещё и почему — так, тогда будешь молодец. :)
В Рефлекторе я, кажется, видел ситуацию, обратную описанной вами. Впрочем, я тоже могу перепутать. Если вы утверждаете, что не ошиблись — не буду спорить.
    class Program
    {
        static void SomeMethod(Func<int, int> otherMethod)
        {
            otherMethod(1);
        }

        static int OtherMethod(int x)
        {
            return x;
        }

        static void Main(string[] args)
        {
            SomeMethod(OtherMethod); // 1
            SomeMethod(x => OtherMethod(x)); // 2
        }
    }


В случае с лямбдой — делегат кешируется, если он может быть закеширован, поэтому это и не всегда видно.
А вариант 1 не кеширует — банально в пользу обратной совместимости (поведение компилятора не меняли, по моему об этом Липперт писал, но я не помню уже точно).

С лямбдами и замыканиями для меня лично, есть более неприятная вещь (код упрощен, но ситуация такая бывает):

        static void MyMethod(int value)
        {
            // 1
            if (value == 2)
            {
                // 2
                SomeMethod((x) => OtherMethod(value));
            }
        }


Сам объект-замыкание-холдер создаётся в (1), а не в (2), как можно было бы предположить.
12. Структуры являются неизменяемыми, когда используются в коллекциях
Дело не колеекциях, а в том, что list[0] вернет rvalue. можете поэксперементировать с обычным методом, возвращающим структуру.
Ну, так там и написано: «В первом случае мы получим ошибку компиляции, поскольку индексатор коллекции это всего-навсего метод, который возвращает копию нашей структуры. Во втором случае мы ошибки не получим, поскольку индексация в массивах это не вызов метода, а обращение именно к нужному элементу.»
Как-то неоднозначно получилось) В общем, и ваш и его ответ, видимо, лучше просто объединить, для полной ясности, что-то вроде «поскольку индексатор коллекции это всего-навсего метод, а методы возвращают структуры в виде типа значения (в отличие от классов, возвращаемых в виде ссылочного типа), возвращаемый результат изменить невозможно...»…
В первом случае мы получим ошибку компиляции, поскольку индексатор коллекции это всего-навсего метод, который возвращает копию нашей структуры. Во втором случае мы ошибки не получим, поскольку индексация в массивах это не вызов метода, а обращение именно к нужному элементу.
Обращение будет к копии экземпляра типа значения, а не к самому экземпляру.

using System;

namespace Tst
{
    struct Node
    {
        public int Value;
    }
    class Program
    {
        static void Main()
        {
            var node = new Node {Value = 1};
            var array = new[] {node};

            array[0].Value = 2;

            Console.WriteLine(node.Value); // Выведет 1
            Console.ReadKey();
        }
    }
}


9. Вы не можете использовать константу с именем value__ в перечислении
Аналогично нельзя использовать методы T get_XXX() и void set_XXX(T) внутри класса, если объявлено свойство T XXX {get; set;}

    public class Node
    {
        public int Value { get; set; }

        public int get_Value()  // error: member with the same 
                                // signature is already declared
        {
            return 1;
        }

        public void set_Value(int value) // error: member with the same 
                                         // signature is already declared
        {
            
        }
    }
С индексатором то же самое. Нельзя использовать Index в классе, в котором объявлено проперти-индексатор. Но можно указать компилятору для поля-индексатора другое название
5. Перечисления могут иметь методы расширения
Более того, даже примитивные типы могут иметь методы расширения.

Кстати, если кому-то до сих пор требуется писать под второй фреймворк, то для использования методов расширения достаточно добавить в любой из проектов всего 4 строчку:
namespace System.Runtime.CompilerServices {
  public class ExtensionAttribute : Attribute {}
}


Иными словами, методы расширения — это полностью фича языка, а не среды исполнения.
UFO just landed and posted this here
Насколько я понимаю, такого понятия как «Private переменная экземпляра класса» просто не существует, есть просто «Private переменная класса» (по крайней мере в С++ так). Поэтому ничего удивительного в данном примере нет.
Не совсем. Сама переменная все равно является сущностью экземпляра, а не класса, а вот уровень доступа к ней действительно ограничен классом, а не экземпляром.
Именно так, я это и имел в виду.
Кстати, читал тут недавно книгу по Scala, там есть уровень доступа private this — переменная доступна только для методов, вызванных для данного экземпляра.
Посоветуйте книжку, пожалуйста. Хочется почитать что-нибудь фундаментальное.
Martin Odersky — Programming in Scala. Не скажу что фундаментально, но подумать есть над чем.
Кстати, отмечу тринадцатый факт, потому что, видимо, все так погрузились в обсуждение деталей статьи, что не заметили его.
Факт 13: В статье под названием «8 фактов, которые вы, возможно, не знали о C#» описаны 12 фактов. Ой, теперь уже 13.
Ну а что вас удивляет? Автор же честно пишет, что «8 фактов (..) вы, возможно, не знали», то есть исходит из того, что минимум 4 факта в статье знает уж точно каждый хабровчанин.
В некотором роде откровением для меня стало лишь наплевательство компилятора на несоблюдение типов инициализируемых полей в случае рекурсивного вызова конструкторов. Но, для иллюстрации применимости этих знаний на практике могу процитировать классика:

Шерлок Холмс: Коперник — знакомая фамилия. Что он сделал?
Доктор Ватсон: Боже мой, так ведь это же он открыл, что Земля вращается вокруг Солнца! Или этот факт вам тоже неизвестен?
Шерлок Холмс: Но мои глаза говорят мне, что скорее Солнце вращается вокруг Земли. Впрочем, может быть, он и прав, ваш этот… как его — Коперник.
Доктор Ватсон: Простите меня, Холмс! Но вы же человек острого ума, это сразу видно! …Как же вы не знаете вещей, которые известны каждому школьнику?!
Шерлок Холмс: Ну, когда я был школьником, я это знал, а потом основательно забыл.
Доктор Ватсон: Вы что, хвастаетесь своим невежеством?! …Но ведь я говорю об элементарных вещах, которые знает каждый!
Шерлок Холмс: Но я-то не каждый, Ватсон, поймите: человеческий мозг — это пустой чердак, куда можно набить всё, что угодно. Дурак так и делает: тащит туда нужное и ненужное. И наконец наступает момент, когда самую необходимую вещь туда уже не запихнёшь. Или она запрятана так далеко, что ее не достанешь. Я же делаю всё по-другому. В моём чердаке только необходимые мне инструменты. Их много, но они в идеальном порядке и всегда под рукой. А лишнего хлама мне не нужно.
Доктор Ватсон: Учение Коперника, по-вашему, хлам?!
Шерлок Холмс: Хорошо. Допустим, Земля вращается вокруг Солнца.
Доктор Ватсон: То есть… то есть… ка́к — допустим?!
Шерлок Холмс: Земля вращается вокруг Солнца. Но мне в моём деле это не пригодится!

Кстати, правда некоторым колет глаза, вон, какие-то редиски меня уже за карму покусали.
Просто менять оригинальное название статьи нехорошо))) А фактов и впрямь 12)
Я думаю перечисления — зло

1. Перечисления повышают безопасность использования типов. Например, при определении константных значений для Interop, предпочтительнее воспользоваться enum, а не множеством из const-членов.
2. Предпочту switch там, где нет необходимости во всяких паттернах типа «стратегии».
3. Флаговые перечисления бывают катастрофически полезными.

Сомневаюсь, что я привёл полный список аргументов «за» использование перечислений.
О каком зле вообще можно говорить, если в самом .NET 100500 перечислений, и с каждой версией их становится всё больше?

Не зря же авторы шарпа вернули в язык плюсовые перечисления, когда придумывали свою джаву с блэкджеком и шлюхами.
Я думаю автор сказал что перечисления зло — потому что они создают проблему с управлением версиями.
При добавлении новых констант в перечисление приходится перекомпилировать код использующий его.
А так спору нет — это полезная штука…
Ещё при локализации обычно возникают некоторые трудности.
Не знаю, откуда берутся такие космические числа в опросе. В топике либо капитанство (первые 8 вопросов), либо совершенно бесполезная информация (большинство последующих). Если в опросе участвовали те, кто реально разрабатывает на шарпе, то это очень печально. :(

1. Индексаторы могут использовать params параметры

Что оно поддерживается — это логичная фича. Почему не поддерживать, если можно передавать несколько аргументов?

2. Строковые литералы, определенные в вашем коде хранятся в единственном экземпляре

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

6. Порядок, в котором вы описываете статические переменные, имеет значение

Это, конечно, любопытная информация, но у любого нормально разраба стоит решарпер, и он будет ругаться. И это верно только для переменных, там по сути код вставляется в конструктор как есть. Константы компилятор разрулит.

8.Спецификация языка C# уже на вашем компьютере

И это сообщается тем, кто думает, что строковые константы создаются каждый раз? :)) Там же много букв.

9. Вы не можете использовать константу с именем value__ в перечислении

Забавный факт. Но бесполезный на 100%.

10. Инициализатор полей позволяет слишком много

Ваш пример можно объянить безо всяких махинаций. Функции безусловно вызывают друг друга — переполнение стека. Соответственно, опять бесполезная информация.

11. Вы можете использовать только C# синонимы при описании базового типа перечисления

Если это не знаешь, то обнаруживаешь при первой попытке использовать. Смысл запоминать отдельно?

12. Структуры являются неизменяемыми, когда используются в коллекциях

А здесь ещё и безграмотное объяснение. Впрочем, здесь опять ошибка компиляции, то есть, если не знаешь, то придётся узнать, когда понадобится.
Согласен на все 100%.
Первый пример тоже бесполезен, как по мне. Я хоть и знаю о такой возможности, но не стал бы использовать такое в своем коде — индексаторы с несколькими параметрами существуют не для таких хаков, код теряет наглядность. Если вы встречаете в коде O[1,2] — это что — попытка получить 1 элемент из двумерного массива или первый и второй элемент из одномерного? Гарантирую, что подавляющее большинство проголосует за двумерный массив.
Читая пункт 5 о том, что перечисления могут иметь методы расширения, испытывал смешанные чувства. Мало того, что КО, так еще и описано настолько узко, что новичка, не ведающего о существовании расширений это только запутает.
Ай, промазал уровнем.

Абсолютно согласен.
Есть редкие и полезные факты. А в этой статье почти всё — редкие и бесполезные факты.

1. 95% программистов НИКОГДА не воспользуются этим видом индексатора. Вообще никогда.
2. Тоже самое хотел сказать. Интернирование настраиваемо. И, кстати, надо разобраться, там, по-моему в разных версиях .NET, настройки по умолчанию разные.
9. Также согласен. Бесполезный факт на 100%.
10. За такие вызовы вообще сразу ссаной тряпкой по морде надо бить.

Вот такие «обнаружатели интересных фактов» потом мучают джуниоров, а то и мидлов на собеседованиях всяким бредом.
UFO just landed and posted this here
Конечно. Но в статье не про кишки, а про безголовость.
Кстати, чтобы быть хорошим разрабом, не надо знать кишки платформы.
Любой разраб, столкнувшись с непонятным поведением, может прогуглить это поведение.
Для хорошего разработчика, который лабает на C++, Haskell, C#.NET, F#.NET всю эту бредятину запомнить невозможно. Лучше Кнута почитать.
Я ни раз видел в подкастах как тот же Roy Osherove натыкается на непонятку и просто гуглит её.
UFO just landed and posted this here
Ничего подобного. Не произойдёт запоминание. Почти всегда, то, что не используется достаточно долго, забывается напрочь. Бывает, что нечто редкое наглухо врезается в память, но это большая редкость.
Причём ж тут бесполезная/небесполезная? Тут описывается, как можно делать, а не как нужно.
О некоторых пунктах я не знал, например, просто потому, что никогда не использовал и даже не задумывался использовать в реальном коде.
Да и вообще, не стоит так нагло выпячивать своё ЧСВ. Я как бы ещё с 2004 года зарабатываю на C# но вопросы 1, 8, 9, 10 у меня не стояли ни разу. А, следовательно, точного ответа я не знал. И что тут печального?
О некоторых пунктах я не знал, например, просто потому, что никогда не использовал и даже не задумывался использовать в реальном коде.

И никогда не задумаетесь использовать. ;) О том, собственно, и речь.

Бесполезная информация забывается. Вы завтра же забудете про это всё, потому что применить невозможно.
Периодически заглядываю в тему на stackoverflow под названием скрытые фичи c#, фактически сборник удобных фишек с индексатором в первом сообщении. Периодически, потому что за один раз сложно всё попробовать и принять.

В индексе клик на название ссылается на MSDN, клик на автора — на его сообщение, часто с примером.
Что ж, в этом вопросе после капитанства идёт хоть что-то полезное:

When debugging, you can type $exception in the Watch\QuickWatch\Immediate window and get all the info on the exception of the current frame. This is very useful if you've got 1st chance exceptions turned on!

string s = string.Format("{0:positive;negative;zero}", i);

RealProxy lets you create your own proxies for existing types

Programmers moving from C/C++ may miss this one:
In C#, % (modulus operator) works on floats!

The extern alias keyword to reference two versions of assemblies that have the same fully-qualified type names.


После 5 страниц утомился…
А знаете ли вы, что при компиляции каждой сборки, в неё «втихую» добавляется особенный класс с зарезервированным именем "<Module>"? В него по идее должны добавляться глобальные функции и константы, которые в C# не поддерживаются. А особенный он потому, что он вообще не имеет базового класса, то есть не наследуется от System.Object. Ещё одним примером класса-сироты является… Правильно, сам System.Object =)
Зачем вы во втором пункте про строковые литералы используете в примере метод String.IsInterned()?
Как по мне, так это только запутает читателя, ведь действие этого метода нужна пояснять на другом примере.
Так же String переопределяет оператор == и он сравнивает не по ссылкам а по значению. (Determines whether two specified strings have the same value.)
А в вашем случае надо использовать простую проверку
Object.ReferenceEquals("what", "wh" + "at")
только сейчас заметил что это перевод
[Conditional(«DEBUG»)] применимо только к классам и методам, в отличие от #if DEBUG.
Добавлю свои 5 копеек.

Код ниже компилируется (пример от Джона Скита):

static void Main()
{
    var foo = "foo";
    foo = 5;
}

// ...

class var
{
    public static implicit operator var(string x)
    {
        return null;
    }

    public static implicit operator var(int x)
    {
        return null;
    }
}


Инициализаторы без new:

class Foo
{
    public Bar Bar;
}

class Bar
{
    public int Value;
}

static void Main()
{
    var foo = new Foo() {Bar = {Value = 5}};
}
Иногда, а особенно после таких статей, завидую «шарперам» белой завистью. Эх, хоть бейте меня ногами… Не удержусь. В Java бы хоть немножко такого… Даже о value-types не мечтаю, но вот хотя бы unsigned…
А зачем value-types в Java? Содержимое «не убегающего» объекта умеет размещаться в стеке, при этом совершенно прозрачно. Ну разве что в массивы объекты как value-types не положить — это так критично?

Вопрос без подвоха — я пишу на C# и считал это такой уж киллерфичей языка.

Вот функциональность крайне полезно, но под java можно либо надеяться на восьмую версию либо осваивать scala.
С использованием value-types улучшается memory reference locality. Если объект собран из нескольких value-объектов, он лежит в памяти одним отрезком и с большой вероятностью прогрузится в кеш-строку при обращении к любому полю. Если объекты-компоненты доступны по ссылкам, обращение к очередному компоненту — прогрузка новой кеш-строки, сотни тактов процессорного времени.
Например, эффективно делать длинную арифметику.
Ну да, в этом есть некоторый смысл…
Всё это конечно интересно, но вопрос на сколько это может быть полезным!
А за код в пункте 10, надо давать 10 суток.
Все это интересно, но все это описывал Рихтер в 3-м издании. Не заметить было трудно, потому как шло под выделенным жирным «Примечание».
Sign up to leave a comment.

Articles