Использование расширений для обхода неинициализированных классов

Всем привет. Решил написать об одной интересной вещи, о которой я узнал на конференции DevCon в докладе Дмитрия Нестерука «Несколько трюков в C#», а именно использование расширений для обхода неинициализированных классов. Если у вас есть свободное время, обязательно посмотрите сам доклад, там затронуто еще несколько очень интересных вещей…

Суть проблемы следующая: очень часто возникает необходимость получить значение из класса, которое находится в объекте другого класса, которое… Для понимания, во схема классов, на которых будет показан принцип работы.

Для любителей кода, вот, что представляют из себя данные классы:
  class User
  {
      public string Name { get; set; }
      public Address Address { get; set; }
  }
  class Address
  {
      public string City { get; set; }
      public string Street { get; set; }
      public Cooldinate Coordinate { get; set; }
  }
  class Cooldinate
  {
      public double Longitude { get; set; }
      public double Latitude { get; set; }
  }
Пусть, для начала, объект пользователя будет инициализирован следующим образом.
var user = new User
           {
               Name = "Andrei",
               Address =
                   new Address
                   {
                       City = "Vitebsk",
                       Coordinate = new Cooldinate {Latitude = 55.11, Longitude = 33.1}
                   }
           };

Проблема заключается в следующем: у нас есть пользователь, мы хотим получить координаты его местоположения, при условии, что они заданы. Как нужно поступить в данном случае?

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

  var latitude = user.Address.Coordinate.Latitude;
  var longitude = user.Address.Coordinate.Longitude;
  Console.WriteLine("{0}, {1}", latitude, longitude);

И да, для текущего случая мы получим необходимые нам координаты, так как все требуемые там классы были инициализированы.

Но не все необходимые нам классы, могут быть инициализированы, например:
var user = new User
           {
               Name = "Andrei",
               Address =
                   new Address
                   {
                       City = "Vitebsk"
                   }
           };

В таком случае user.Address.Coordinate будет равен null и при попытке чтения его свойств произойдёт исключение.

Обычно в таком случае помогают конструкции подобного вида:
  var latitude = "not defined";
  var longitude = "not defined";
  if (user.Address != null)
  {
      if (user.Address.Coordinate != null)
      {
          latitude = user.Address.Coordinate.Latitude.ToString();
          longitude = user.Address.Coordinate.Longitude.ToString();
      }
  }

Это решает проблему неинициализированных классов, но при этом приходится писать очень много ненужного кода. Данная проблема отпадёт с началом повсеместного использования C# 6, но пока приходится прибегать к ухищрениям. Один из вариантов, это использования расширения классов.

Создадим статический класс ClassNullExtension, содержащий статический метод With, представляющий из себя следующее:

 
public static TOut With<tin, tout="">(this TIn self, Func<tin, tout=""> f, TOut failValue = null)
     where TIn : class
     where TOut : class
 {
     return self == null ? failValue : f(self);
 }

Что именно в нём описано:

TIn и TOut это типа класса, которые будет переданы в метод With и возвращены соответственно.
Строки where TIn : class where TOut : class показывают, что это должны быть именно классы.

failValue – это значение возвращаемое в случае, если класс равен null.

Func – это делагат, который, в данном случае инкапсулирует метод с параметром типа TIn и возвращающего значение типа TOut.

При использовании данного расширения мы можем писать код вида:

  var coordinate = user.With(x => x.Address).With(x => x.Coordinate);
  var latitude = coordinate == null ? "not defined" : coordinate.Latitude.ToString();
  var longitude = coordinate == null ? "not defined" : coordinate.Longitude.ToString();

Данное расширение можно легко менять и подстраивать непосредственно под конкретную задачу.

Приятного программирования.

Комментарии (1) -

Андрей, доброго времени суток.
В сигнатуре метода public static TOut With<tin, tout="">(this TIn self, Func<tin, tout=""> f, TOut failValue = null) синтаксические ошибки. Поправьте пожалуйста.

Добавить комментарий