Всем привет. Решил написать об одной интересной вещи, о которой я узнал на конференции 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();
Данное расширение можно легко менять и подстраивать непосредственно под конкретную задачу.
Приятного программирования.