Pull to refresh

Трюки языка C#

Reading time4 min
Views20K

ИМХО, вместо того, чтобы вкручивать собеседникам мозги насчет заумной семантики event’ов и делегатов или же спрашивать когда написание new IEntity() легально, можно задать вопрос попроще – например “приведите пример необычного поведения или использования языка C#”. Вот несколько примеров, которые пришли в голову в качестве возможных ответов.




Операторы | и & против || и &&


Обе пары операторов могут быть применены к переменным типа bool. Единственная разница – одиночные операторы не сокращают количество вычислений если результат очевиден:



bool b = false && a(); // a() not called
bool c = false & a();  // a() called

Передача null в качестве исключения


Как ни странно, передача null в качестве исключения приводит к выбросу NullReferenceException:



try {
  throw null;
catch (NullReferenceException) {
  // will be caught!
}

Странная стрелка при перечислении


Можно неплохо озадачить коллег если вместо типичного for при отсчете использовать while с необычной стрелкой. Синтаксис абсолютно легален! Кстати – помнится тут на Хабре это кого-то смутило…



public IEnumerable<Foo> Create(int count)
{
  while (count --> 0)
    yield return new Foo();
}

Полезный оператор ??


Можно воспользоваться оператором ?? для ленивой инициализации объектов:



private IList<Person> people;
public IList<Person> People {
  get {
    return people ?? 
      (people = new List<Person>());
  }
}

Также можно использовать оператор для проверки цепочек значений:



int err(int? alpha, int? beta)
{
  return alpha ?? beta ?? -1;
}

Хотя имхо для ленивости подходит и АОР. Или Boo :)



Методы расширения


Методы расширения можно применять не только к конкретным типам но и к интерфейсам. Это позволяет добавлять классам поведение не вклиниваясь в их иерархию наследования:



public static void T DeepCopy<T>
  (this ICopyable<T> self)
  where T : classnew()
  {
    ⋮
  }
}

В принципе расширения на интерфейсы это “поведенческие примеси” (behavior mixing). В отличии от F#, свойства примешивать нельзя, хотя если уж очень захотелось (warning: not recommended), можно написать что-нть вот такое:



static class ExtensionMethods
{
  private static Dictionary<WeakReference, object> tags = 
    new Dictionary<WeakReference, object>();
  public static void SetTag(this object obj, object tagValue)
  {
    Cleanup();
    var found = tags.Select(o => o.Key)
      .Where(k => ReferenceEquals(obj, k.Target));
    if (found.Count() > 0)
    {
      tags[found.Single()] = tagValue;
    }
    else
      tags[new WeakReference(obj)] = tagValue;
  }
  public static T GetTag<T>(this object obj)
  {
    return (T)tags.Where(o => ReferenceEquals(o.Key.Target, obj)).First().Value;
  }
  private static void Cleanup()
  {
    var toRemove = tags.Select(o => o.Key).Where(p => !p.IsAlive);
    foreach (var tr in toRemove)
      tags.Remove(tr);
  }
}

Лямбды вместо строк


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



public static string PropertyName<T>
  (this object obj, Expression<Func<T>> p)
{
  return (p.Body as MemberExpression).Member.Name;
}

Использовать эту конструкцию можно так:



lblName.DataBindings.Add(
  PropertyName(() => Name), thisnull);

Аналогичные манипуляции возможны и с методами. Да и вообще с чем угодно :)



Возврат значения из try-finally


Можно смело делать воврат из try-finally блока. При этом, компилятор закэширует возвращаемое значение, и оно будет возвращено только после исполнения finally-блока.



try {
  a();
  return x; // cached

finally {
  b(); // called before x is returned
}

Креативное использование Dispose()


Интерфейс IDisposable можно использовать для создания блоков, вход и выход в которые нужно перехватить. Например, менять курсор на время выполнения операции:



class WaitCursor : IDisposable {
  private Cursor oldCursor;
  public WaitCursor(Cursor oldCursor) {
    this.oldCursor = oldCursor;
    SetCursor(Cursor.Wait);
  }
  public void Dispose() {
    SetCursor(oldCursor);
  }
}

Использовать этот конструкт можно так:



using (new WaitCursor(this.Cursor))
{
  // long operation
}

Хотя для таких конструктов можно и АОР использовать.



Делегат-заглушка


Дабы не проверять событие на null, можно при декларировании подписать на событие делегат-заглушку:
public event MyClickHandler Click = delegate {};

В заключение


То, что работодатели напрягают собеседников всякими бредовыми задачками типа “единичный случай” это безусловно очень хорошо :) потому что нам становится легче с ними конкурировать за классных разработчиков.



Критика welcome!

Tags:
Hubs:
Total votes 94: ↑77 and ↓17+60
Comments92

Articles