Пользователь
0,0
рейтинг
7 мая 2013 в 00:33

Разработка → 8 фактов, которые вы, возможно, не знали о C# перевод

C#*, .NET*
Вот несколько необычных фактов о языке C#, о которых знают лишь немногие разработчики.

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


Мы все знаем, как обычно выглядят индексаторы x = something["a"], а так же код необходимый для его реализации:

public string this[string key]
 {
   get { return internalDictionary[key]; }
 }

Но знали ли вы, что для доступа к элементам вы можете использовать params параметры x = something["a", "b", "c", "d"]?
Просто напишите ваш индексатор следующим образом:

public IEnumerable<string> this[params string[] keys]
 {
   get { return keys.Select(key => internalDictionary[key]).AsEnumerable(); }
 }

Отличная новость заключается в том, что вы можете использовать несколько индексаторов совместно в одном классе. Если кто-то передаст массив или несколько аргументов в качестве параметра он получит IEnumerable как результат, но вызывая, индексатор с одним параметром он получит единственное значение.

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


Многие разработчики думают, что следующий код:

if (x == "" || x == "y")

будет создавать пару строк каждый раз. Нет, не будет. C# как и многие другие языки программирования использует интернирование строк, и каждая литеральная строка используемая в вашем приложении кладется в специальный список (хеш таблицу), называемый пулом строк, откуда и берется ссылка на нее во время выполнения.

Вы можете использовать метод String.IsInterned, чтобы определить находится ли строка в пуле интернирования в настоящий момент, но помните, что выражение String.IsInterned("what") == "what" всегда будет истинно.
String.IsInterned("wh" + "at") == "what" так же будет всегда истинно, спасибо компилятору за оптимизацию. String.IsInterned(new string(new char[] {'w','h','a','t'}) == new string(new char[] {'w','h','a','t'}) будет истинно только, если литеральная строка «what» уже была использована в вашем коде или если она вручную была добавлена в пул интернирования.

Если у вас есть классы, которые регулярно используют строки, рассмотрите возможность использования метода String.Intern для добавления их в пул строк. Однако будьте осторожны, так как в этом случае они хранятся до завершения приложения, поэтому используйте String.Intern аккуратно. Для добавления строки в пул просто вызовите метод String.Intern(someClass.ToString()). Другая предосторожность заключается в том, что (object)"Hi" == (object)"Hi" всегда истинно, спасибо интернированию. Попробуйте проверить этот код в окне интерпретации, и результат будет отрицательным, так как отладчик не интернирует ваши строки.

3. Приведение типов к менее специализированным не мешает использовать их настоящий тип


Прекрасным примером этого является свойство возвращающее IEnumerable, настоящий тип которого есть List, например:

private readonly List<string> internalStrings = new List<string>();
public IEnumerable<string> AllStrings { get { return internalStrings; }

Вы думаете, что никто не может изменить список строк. Увы, это сделать слишком просто:

((List<string>)x.AllStrings).Add("Hello");

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

4. Переменные в методах могут иметь область видимости ограниченную фигурными скобками


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

Однако это не мешает вам их использовать после того, как они перестали быть нужными. Мы можем описать временные переменные, используя ключевые слова for/if/while/using. Вы будете удивлены, но вы так же можете описать временные переменные, используя фигурные скобки без ключевых слов для получения того же результата:

private void MultipleScopes()
 {
   { var a = 1; Console.WriteLine(a); }
   { var b = 2; Console.WriteLine(a); }
 }

Данный код не компилируется из-за второй строчки, потому что она обращается к переменной, которая заключена в фигурные скобки. Такое разбиение метода на части фигурными скобками достаточно полезно, но гораздо лучшее решение — разделить метод, на меньшие, используя метод рефакторинга: выделение метода (extract method).

5. Перечисления могут иметь методы расширения


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

enum Duration { Day, Week, Month };
static class DurationExtensions
 {
   public static DateTime From(this Duration duration, DateTime dateTime)
    {
     switch (duration)
      {
         case Duration.Day: return dateTime.AddDays(1);
         case Duration.Week: return dateTime.AddDays(7);
         case Duration.Month: return dateTime.AddMonths(1);
         default: throw new ArgumentOutOfRangeException("duration");
        }
    }
 }

Я думаю перечисления — зло, но, по крайней мере, методы расширения позволяют избавиться от некоторых условных проверок (switch/if) в вашем коде. Не забудьте проверить, что значение перечисления находится в нужном диапазоне.

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


Некоторые люди утверждают, что переменные располагаются в алфавитном порядке, и что есть специальные инструменты способные изменить их порядок за вас. Однако существует сценарий, при котором изменение порядка может сломать ваше приложение.

static class Program
 {
   private static int a = 5;
   private static int b = a;

   static void Main(string[] args)
    {
       Console.WriteLine(b);
    }
 }

Этот код будет печатать число 5. Если поменять местами описание переменных a и b, он будет печатать 0.

7. Private переменная экземпляра класса может быть доступна в другом экземпляре данного класса


Вы можете подумать, что следующий код работать не будет:

class KeepSecret
 {
   private int someSecret;
   public bool Equals(KeepSecret other)
    {
      return other.someSecret == someSecret;
    }
  }

Легко подумать, что private означает только данный экземпляр класса, может иметь доступ к нему, но в реальности это означает, что только данный класс имеет доступ к нему… включая другие экземпляры данного класса. На самом деле это очень полезно при реализации некоторых методов сравнения.

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


Если у вас установлена Visual Studio вы можете найти спецификацию в папке VC#\Specifications. VS 2011 поставляется вместе со спецификацией языка C# 5.0 в формате Word.

Она полна множества интересных фактов таких как:
  • i = 1 — атомарно (потокобезопастно) для int но не для long;
  • Вы можете использовать операторы & и |к обнуляемому логическому типу (bool?), обеспечивая совместимость с троичной логикой SQL;
  • Использование [Conditional(«DEBUG»)] предпочтительнее, чем #if DEBUG.


А тем из вас кто говорит, «я знал все/большинство из этого» я говорю: «Где вы, когда я нанимаю разработчиков?». Серьезно, достаточно трудно найти C# разработчика с хорошим знанием языка.

От себя добавлю пару фактов:

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


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

Следующее перечисление:

public enum Language
 {
   C,
   Pascal,
   VisualBasic,
   Haskell
 }

компилятор видит примерно так:

public struct Language : Enum
 {
   //Открытые константы, определяющие символьные имена и значения
   public const Language C = (Language)0;
   public const Language Pascal = (Language)1;
   public const Language VisualBasic = (Language)2;
   public const Language Haskell = (Language)3;

   //Открытое поле экземпляра со значением переменной Language
   //Код с прямой ссылкой на этот экземпляр невозможен
    public Int32 value__;
  }

Теперь понятно, что имя переменной value__ просто зарезервировано для хранения текущего значения перечисления.

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


Все мы знаем, как работает инициализатор полей.

class ClassA
 {
   public string name = "Tim";
   public int age = 20;

   public ClassA()
    { }

   public ClassA(string name, int age)
    {
     this.name = name;
     this.age = age;
    }
  }

Код инициализации просто помещается в каждый из конструкторов.

class ClassA
  {
    public string name;
    public int age;

    public ClassA()
    {
      this.name = "Tim";
      this.age = 20;
      base..ctor();
    }

    public ClassA(string name, int age)
    {
      this.name = "Tim";
      this.age = 20;
      base..ctor();
      this.name = name;
      this.age = age;
    }
  }

Но если один конструктор вызывает другой, то инициализация помещается только в вызываемый конструктор…

class ClassA
 {
   public string name = "Tim";
   public int age = 20;

   public ClassA()
    { }

   public ClassA(string name, int age):this()
    {
     this.name = name;
     this.age = age;
    }
 }

что равносильно

class ClassA
  {
    public string name;
    public int age;

    public ClassA()
    {
      this.name = "Tim";
      this.age = 20;
      base..ctor();
    }

    public ClassA(string name, int age)
    {
      this..ctor();
      this.name = name;
      this.age = age;
    }
  }

А теперь вопрос, что будет, если конструкторы вызывают друг друга? Куда компилятор решит поместить код инициализации?

Ответ: компилятор решит, что раз первый конструктор вызывает второй, то в первый инициализацию не помещу. Аналогичные выводы он сделает со вторым конструктором. В результате код инициализации не будет помещен ни в один конструктор… А раз его не надо никуда помещать, то зачем тогда его проверять на наличие ошибок? Таким образом в коде инициализации можно писать все, что угодно. Например,

class ClassA
 {
   public string name = 123456789;
   public int age = string.Empty;

   public ClassA():this("Alan", 25)
    { }

   public ClassA(string name, int age):this()
    {
     this.name = name;
     this.age = age;
    }
 }

Такой код скомпилируется без проблем, однако грохнется с переполнением стека при создании экземпляра данного класса.

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


Как известно при описании перечисления мы можем явно указать базовый тип, лежащий в его основе. По умолчанию — это int.

Мы можем написать так

public enum Language : int
 {
   C,
   Pascal,
   VisualBasic,
   Haskell
 }

однако мы не можем написать так

public enum Language : Int32
 {
   C,
   Pascal,
   VisualBasic,
   Haskell
 }

Компилятор требует именно C# синоним типа.

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


Как известно структуры в .NET являются значимыми типами, то есть они передаются по значению (копируются), именно поэтому изменяемые структуры — зло. Однако тот факт, что изменяемые структуры — зло еще не говорит о том, что их нельзя изменять. По умолчанию структуры являются изменяемыми, то есть их состояние можно изменить. Но если структуры используются в коллекциях, то они перестают быть изменяемыми. Рассмотрим код:

struct Point
 {
   public int x;
   public int y;
 }

static void Main(string[] args)
 {
    var p = new Point();  p.x = 5; p.y = 9;
    var list = new List<Point>(10);
    list.Add(p);
    list[0].x = 90;//ошибка компиляции

    var array = new Point[10];
    array[0] = p;
    array[0].x = 90;//все ок
}

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

Спасибо за прочтение.
Надеюсь, статья оказалась полезной.
А теперь вопрос к C# разработчикам! Чего вы не знали из вышеуказанного?

Проголосовало 1109 человек. Воздержалось 1259 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Перевод: Damien Guard
Гуев Тимур @timyrik20
карма
112,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (66)

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

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

          // 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) кода.
    • +3
      Вы серьезно были удивлены вас этим, особенно (1)?
      • +1
        Да, я думал что компилятор делает это исключительно ради оптимизации. Тот факт, что инструкция ldfld при обращении к такому полю кидает MissingFieldException, оказался для меня полной неожиданностью.
    • +1
      Автор конечно погреет свое самолюбие на почве того, как много людей оказались невежественней. Но все равно спасибо, я для себя действительно что-то открыл, чтение было полезным. На редкость.

      От себя:
      Методы расширения имеют небольшую особенность на фоне обычных методов — мы можем обращаться к методам, даже если объекты существуют как null, в то время как обычный метод в классе при таком выпадает. Проще говоря мы не можем создать рабочий метод в классе аля IsNull() { return a == null; }, в то время как метод расширения будет так работать. Например именно поэтому оно и работает
      • +1
        Это потому, что метод-расширение, преобразуется при компиляции в вызов соотв. статического метода из класса.
        т.е. есть расширение
        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
        }
        • –1
          Именно. И порой для чтения они могут быть более приятными :)
    • 0
      Почему вы пишите «Поля, помеченные как const»? Поля это поля. Константы это константы. У них разные реализации и назначение.
      • +4
        С точки зрения CLR «константа» — это не отдельная сущность, а поле с выставленным флагом IsLiteral.
        Можете убедиться в этом с помощью Reflection:
        typeof(int).GetField("MaxValue")
        • –1
          Угу, только главное тут не то как это выглядит с т.з. Reflection, а то что при обращении к public const в другой сборке. И поэтому это уже не поле.

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

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

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

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

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

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

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

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

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

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

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

      Проще говоря, extension-методы — это не фича, это сахар.
    • 0
      Пункт 2: На самом деле не совсем так.
      В случае вызова SomeMethod(OtherMethod) — будет всегда создаваться делегат.
      В случае вызова SomeMethod(x => OtherMethod(x)) — делегат будет кешироваться.
      • 0
        С точностью до наоборот…
        • 0
          Очевидно, стоит или перечитать мой комментарий, или проверить самому. А если напишешь ещё и почему — так, тогда будешь молодец. :)
          • 0
            В Рефлекторе я, кажется, видел ситуацию, обратную описанной вами. Впрочем, я тоже могу перепутать. Если вы утверждаете, что не ошиблись — не буду спорить.
            • +1
                  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), как можно было бы предположить.
  • +2
    12. Структуры являются неизменяемыми, когда используются в коллекциях
    Дело не колеекциях, а в том, что list[0] вернет rvalue. можете поэксперементировать с обычным методом, возвращающим структуру.
    • 0
      Ну, так там и написано: «В первом случае мы получим ошибку компиляции, поскольку индексатор коллекции это всего-навсего метод, который возвращает копию нашей структуры. Во втором случае мы ошибки не получим, поскольку индексация в массивах это не вызов метода, а обращение именно к нужному элементу.»
      • +1
        Как-то неоднозначно получилось) В общем, и ваш и его ответ, видимо, лучше просто объединить, для полной ясности, что-то вроде «поскольку индексатор коллекции это всего-навсего метод, а методы возвращают структуры в виде типа значения (в отличие от классов, возвращаемых в виде ссылочного типа), возвращаемый результат изменить невозможно...»…
  • +3
    В первом случае мы получим ошибку компиляции, поскольку индексатор коллекции это всего-навсего метод, который возвращает копию нашей структуры. Во втором случае мы ошибки не получим, поскольку индексация в массивах это не вызов метода, а обращение именно к нужному элементу.
    Обращение будет к копии экземпляра типа значения, а не к самому экземпляру.

    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
            {
                
            }
        }
    
    • 0
      С индексатором то же самое. Нельзя использовать Index в классе, в котором объявлено проперти-индексатор. Но можно указать компилятору для поля-индексатора другое название
  • +7
    5. Перечисления могут иметь методы расширения
    Более того, даже примитивные типы могут иметь методы расширения.

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


    Иными словами, методы расширения — это полностью фича языка, а не среды исполнения.
  • +4
    > Private переменная экземпляра класса может быть доступна в другом экземпляре данного класса
    Это обычное поведение ООП. В Java и PHP это так же.
    Более того, классы могут быть разные, и они будут иметь доступ к protected-переменным друг друга, если она объявлена в их общем предке.
    • +1
      Насколько я понимаю, такого понятия как «Private переменная экземпляра класса» просто не существует, есть просто «Private переменная класса» (по крайней мере в С++ так). Поэтому ничего удивительного в данном примере нет.
      • +1
        Не совсем. Сама переменная все равно является сущностью экземпляра, а не класса, а вот уровень доступа к ней действительно ограничен классом, а не экземпляром.
        • 0
          Именно так, я это и имел в виду.
          Кстати, читал тут недавно книгу по Scala, там есть уровень доступа private this — переменная доступна только для методов, вызванных для данного экземпляра.
          • 0
            Посоветуйте книжку, пожалуйста. Хочется почитать что-нибудь фундаментальное.
            • 0
              Martin Odersky — Programming in Scala. Не скажу что фундаментально, но подумать есть над чем.
  • +9
    Кстати, отмечу тринадцатый факт, потому что, видимо, все так погрузились в обсуждение деталей статьи, что не заметили его.
    Факт 13: В статье под названием «8 фактов, которые вы, возможно, не знали о C#» описаны 12 фактов. Ой, теперь уже 13.
    • +4
      Ну а что вас удивляет? Автор же честно пишет, что «8 фактов (..) вы, возможно, не знали», то есть исходит из того, что минимум 4 факта в статье знает уж точно каждый хабровчанин.
      • +1
        В некотором роде откровением для меня стало лишь наплевательство компилятора на несоблюдение типов инициализируемых полей в случае рекурсивного вызова конструкторов. Но, для иллюстрации применимости этих знаний на практике могу процитировать классика:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      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 страниц утомился…
  • +3
    А знаете ли вы, что при компиляции каждой сборки, в неё «втихую» добавляется особенный класс с зарезервированным именем "<Module>"? В него по идее должны добавляться глобальные функции и константы, которые в C# не поддерживаются. А особенный он потому, что он вообще не имеет базового класса, то есть не наследуется от System.Object. Ещё одним примером класса-сироты является… Правильно, сам System.Object =)
  • 0
    Зачем вы во втором пункте про строковые литералы используете в примере метод String.IsInterned()?
    Как по мне, так это только запутает читателя, ведь действие этого метода нужна пояснять на другом примере.
    Так же String переопределяет оператор == и он сравнивает не по ссылкам а по значению. (Determines whether two specified strings have the same value.)
    А в вашем случае надо использовать простую проверку
    Object.ReferenceEquals("what", "wh" + "at")
    • 0
      только сейчас заметил что это перевод
  • 0
    [Conditional(«DEBUG»)] применимо только к классам и методам, в отличие от #if DEBUG.
  • 0
    В блоге Эрика Липперта этих фактов на целую книгу наберется. Например про строки и транзитивность ==.
  • +3
    Добавлю свои 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}};
    }
    
  • +3
    Иногда, а особенно после таких статей, завидую «шарперам» белой завистью. Эх, хоть бейте меня ногами… Не удержусь. В Java бы хоть немножко такого… Даже о value-types не мечтаю, но вот хотя бы unsigned…
    • 0
      А зачем value-types в Java? Содержимое «не убегающего» объекта умеет размещаться в стеке, при этом совершенно прозрачно. Ну разве что в массивы объекты как value-types не положить — это так критично?

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

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

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.