Когда вы используете область без инструкции в С#?

Совсем недавно я узнал, что вы можете сделать это на С#:

{
    // google
    string url = "#";

    if ( value > 5 )
        url = "http://google.com";

    menu.Add( new MenuItem(url) );
}
{
    // cheese
    string url = "#"; // url has to be redefined again, 
                      // so it can't accidently leak into the new menu item

    if ( value > 45 )
        url = "http://cheese.com";

    menu.Add( new MenuItem(url) );
}

вместо i.e.:

    string url = "#";

    // google
    if ( value > 5 )
        url = "http://google.com";

    menu.Add( new MenuItem(url) );


    // cheese
    url = "#"; // now I need to remember to reset the url

    if ( value > 45 )
        url = "http://cheese.com";

    menu.Add( new MenuItem(url) );

Это может быть плохой пример, который можно решить многими другими способами.

Существуют ли какие-либо шаблоны, где функция "scope without statement" является хорошей практикой?

Ответ 1

Если вы спросите, почему они реализовали эту функцию?

Я разработал свой собственный язык, и я обнаружил, что синтаксис "scope without a statement" очень легко проникает в грамматику. Это побочный эффект, если хотите, при написании грамматики, которая просто хорошо работает и легко читается.

На моем языке у меня такая же функциональность, и я никогда не "специально" для него явно - Я получил его бесплатно. Я никогда не садился за стол и думал: "О, не было бы здорово иметь такую" особенность "?". Хек - сначала я даже не знал, что моя грамматика допускает это.

'{}' - это "составной оператор", потому что это упрощает синтаксис для все места, которые вы хотите использовать (условные обозначения, тела контуров, и т.д.)... и потому, что это позволяет вам оставить фигурные скобки, когда один ( "if (a < 10) ++ a;" и тому подобное).

Тот факт, что он может быть использован везде, прямо из этого; это безобидно, а иногда полезно, как и другие ответы сказали. "Если он не сломался, не исправляйте его. - Кешлам.

Итак, вопрос заключается не столько в том, "зачем они его реализовали", сколько в том, "почему они не запретили это/почему они разрешили это?"

Могу ли я запретить эту конкретную функциональность на моем языке? Конечно, но я не вижу причин - это будет дополнительная стоимость для компании.

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

Та же функциональность есть в С++, которая фактически имеет некоторые варианты использования - она ​​позволяет дефинитивно запускать финализатор объекта, если объект выходит за пределы области видимости, хотя это не относится к С#.


Тем не менее, я не использовал этот специальный синтаксис в течение 4 лет С# ( embedded-statement → block, когда речь шла о конкретных терминах), и я не видел, чтобы он использовался где угодно, Ваш пример - попросить рефакторировать методы.

Взгляните на грамматику С#: http://msdn.microsoft.com/en-us/library/aa664812%28v=vs.71%29.aspx

Кроме того, как сказал Джеппе, я использовал синтаксис "{}", чтобы убедиться, что каждый блок case в конструкции "switch" имеет отдельную локальную область:

int a = 10;
switch(a)
{
    case 1:
    {
        int b = 10;
        break;
    }

    case 2:
    {
        int b = 10;
        break;
    }
}

Ответ 2

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


Позднее добавление:

Локальные блоки области { ... }, присутствующие в источнике С#, не кажутся релевантными для полученного байт-кода IL. Я пробовал этот простой пример:

static void A()
{
    {
        var o = new object();
        Console.WriteLine(o);
    }

    var p = new object();
    Console.WriteLine(p);
}

static void B()
{
    var o = new object();
    Console.WriteLine(o);

    var p = new object();
    Console.WriteLine(p);
}


static void C()
{
    {
        var o = new object();
        Console.WriteLine(o);
    }

    {
        var o = new object();
        Console.WriteLine(o);
    }
}

Это было скомпилировано в режиме Release (включена оптимизация). Полученный IL в соответствии с IL DASM:

.method private hidebysig static void  A() cil managed
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init ([0] object o,
           [1] object p)
  IL_0000:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_000c:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0011:  stloc.1
  IL_0012:  ldloc.1
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0018:  ret
} // end of method LocalScopeExamples::A

 

.method private hidebysig static void  B() cil managed
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init ([0] object o,
           [1] object p)
  IL_0000:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_000c:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0011:  stloc.1
  IL_0012:  ldloc.1
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0018:  ret
} // end of method LocalScopeExamples::B

 

.method private hidebysig static void  C() cil managed
{
  // Code size       25 (0x19)
  .maxstack  1
  .locals init ([0] object o,
           [1] object V_1)
  IL_0000:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_000c:  newobj     instance void [mscorlib]System.Object::.ctor()
  IL_0011:  stloc.1
  IL_0012:  ldloc.1
  IL_0013:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0018:  ret
} // end of method LocalScopeExamples::C

Выводы:

  • Локальные объявления области не существуют в любой форме в байт-коде IL.
  • Компилятор С# выберет новые имена для локальных переменных, которые в противном случае были бы в конфликте (вторая из переменных o в методе C).
  • Люди, которые чувствуют (см. другие ответы и комментарии к вопросу), что сборщик мусора может выполнить свою работу раньше, когда они вводят локальную область в источник С#, являются неправильными.

Я также попытался скомпилировать это в режиме отладки (без оптимизации). Начало и конец локальной области видимости отображаются только как nop инструкция ( "Нет операции" ). В некоторых случаях две идентично названные локальные переменные из разных локальных областей были сопоставлены с одной и той же локальной переменной в IL, например, с помощью метода С# с именем C выше. Такое "объединение" двух переменных возможно только в том случае, если их типы совместимы.

Ответ 3

Вы можете использовать {} для переименования имен переменных (даже для разных типов):

{
   var foo = new SomeClass();
}
{
   Bar foo = new Bar(); // impairs readability
}

Однако перепрофилирование переменных таким образом будет путать читаемость.

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

Изменить

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

menu.Add( value > 5 
            ? new MenuItem("http://google.com")
            : new MenuItem("#"));

menu.Add( value > 45 
            ? new MenuItem("http://cheese.com")
            : new MenuItem("#"));

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

(или new MenuItem(value > 45 ? "http://cheese.com" : "#")) или создайте перегрузку MenuItem по умолчанию # или переместите создание MenuItem в метод factory и т.д.)

Edit
Re: Scope не влияет на продолжительность жизни

Область может использоваться в рамках метода для ограничения продолжительности жизни дорогих объектов

В моем первоначальном сообщении неправильно указано, что локальная область может использоваться для воздействия на продолжительность жизни объектов. Это неверно, как для сборки DEBUG, так и RELEASE, и независимо от того, перераспределено ли имя переменной или нет, как продемонстрировано разборкой Jeppe IL и этими Модульные тесты здесь. Спасибо Джеппе за это. Кроме того, Лассе подчеркивает, что даже без явного выхода из сферы действия переменные, которые больше не используются, будут иметь право на сбор мусора в сборках релизов.

TL; DR. Хотя использование незапрошенной области видимости может помочь логическому использованию области видимости переменной для человека, это не влияет на то, является ли объект доступным для сбора, в рамках того же метода.

i.e в приведенном ниже коде, просмотр и даже повторное назначение переменной foo ниже абсолютно не влияет на продолжительность жизни.

void MyMethod()
{
  // Gratuituous braces:
  {
      var foo = new ExpensiveClass();
  }
  {
      Bar foo = new Bar(); // Don't repurpose, it impairs readability!
  }
  Thread.Sleep(TimeSpan.FromSeconds(10));
  GC.Collect();
  GC.WaitForPendingFinalizers();
  <-- Point "X"
}

В точке X:

  • В строке DEBUG ни одна из переменных foo не будет собрана, несмотря на хакерские попытки побудить GC сделать это.
  • В сборке RELEASE оба foos будут иметь право на сбор, как только они не понадобятся, независимо от области действия. Время сбора, конечно, должно быть предоставлено на собственные устройства.

Ответ 4

Этот шаблон практически не влияет на время выполнения С#, поэтому это просто эстетическая вещь (сравните с С++, где мы регулярно используем этот шаблон с RAII для охвата таких вещей, как блокировки).

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

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

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

// Add a menu item for Google, if value is high enough.
{
    string url = "#";

    if ( value > 5 )
        url = "http://google.com";

    menu.Add( new MenuItem(url) );
}

// Add a menu item for Cheese, if the value is high enough
{
    // cheese
    string url = "#";

    if ( value > 45 )
        url = "http://cheese.com";

    menu.Add( new MenuItem(url) );
}

Как указано выше, в С# это чисто стилистическая. Не стесняйтесь использовать свой собственный стиль, где это имеет смысл.

Ответ 5

{
    string f = "hello";
}

Просто выглядит странно.

Очевидно, что методы нуждаются в них:

private void hmm() {}

И инструкции switch:

switch(msg)
{
    case "hi":
    // do something
    break;

    // ...
}

И даже если, foreach, while заявления...

if(a == 1)
{
    bool varIsEqual = true;
    isBusy = true;
    // do more work
}

Но если у вас есть только 1 оператор в цикле или в инструкции if, вам не нужны скобки:

if("Samantha" != "Man")
    msg = "Phew!";