Здравствуйте, уважаемые читатели. Сегодня мы снова погрузимся в
удивительный мир C# 6.0 и рассмотрим
такие фишки, как использование nameof оператора, dictionary initialization, а также
null propagation operator. Буду писать в
своем любимом стиле, когда часть кода на языке C# сравниваю с тем кодом, который генерируется в IL. Делаю это для того чтобы помочь разработчикам, которые работают с IL-кодом и любят копаться в том, как
работает та или иная фишка языка, понять, что же все-таки происходит на
самом деле.
nameof
Начнем, пожалуй, с оператора nameof. Его предназначение и использование покажу на
примере. Думаю, вам неоднократно приходилось видеть код, подобный коду внизу,
когда вам нужно было записать некую магическую в строку в ArgumentNullException или другой тип ошибки.
public static void
Print(Point point)
{
if (point == null)
throw new ArgumentException("point");
Console.WriteLine("coordinate X = {0}, coordinate Y = {1}", point.X, point.Y);
}
public class Point
{
public int
X { get; set; }
public int
Y { get; set; }
}
Думаю, в вашем проекте хватает такого кода, в котором вы используете
подобные “магические строки”. В C# 6 появилась
возможность достать имя типа с использованием оператора nameof. Сейчас это выглядит так:
public static void
Print(Point point)
{
if (point == null)
throw new ArgumentException(nameof(point));
WriteLine("coordinate X = {0}, coordinate Y = {1}", point.X, point.Y);
}
Заметьте, у вас пропала магическая строка “point”, а вы достаете имя типа с переданного аргумента.
Если вы посмотрите на метод Print в IL-коде, то увидите, что у вас просто копируется имя
переменной.
.method public hidebysig static
void Print (
class NewSharpFeatures.Point point
) cil managed
{
// Method begins at RVA 0x20f0
// Code size 59 (0x3b)
.maxstack 3
.locals init (
[0] bool
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldnull
IL_0003: ceq
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: brfalse.s IL_0014
IL_0009: ldstr "point"
IL_000e: newobj instance void [mscorlib]System.ArgumentException::.ctor(string)
IL_0013: throw
IL_0014: ldstr "coordinate X = {0}, coordinate Y = {1}"
IL_0019: ldarg.0
IL_001a: callvirt instance int32 NewSharpFeatures.Point::get_X()
IL_001f: box [mscorlib]System.Int32
IL_0024: ldarg.0
IL_0025: callvirt instance int32 NewSharpFeatures.Point::get_Y()
IL_002a: box [mscorlib]System.Int32
IL_002f: call string [mscorlib]System.String::Format(string, object, object)
IL_0034: call void [mscorlib]System.Console::WriteLine(string)
IL_0039: nop
IL_003a: ret
} // end of method Program::Print
void Print (
class NewSharpFeatures.Point point
) cil managed
{
// Method begins at RVA 0x20f0
// Code size 59 (0x3b)
.maxstack 3
.locals init (
[0] bool
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldnull
IL_0003: ceq
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: brfalse.s IL_0014
IL_0009: ldstr "point"
IL_000e: newobj instance void [mscorlib]System.ArgumentException::.ctor(string)
IL_0013: throw
IL_0014: ldstr "coordinate X = {0}, coordinate Y = {1}"
IL_0019: ldarg.0
IL_001a: callvirt instance int32 NewSharpFeatures.Point::get_X()
IL_001f: box [mscorlib]System.Int32
IL_0024: ldarg.0
IL_0025: callvirt instance int32 NewSharpFeatures.Point::get_Y()
IL_002a: box [mscorlib]System.Int32
IL_002f: call string [mscorlib]System.String::Format(string, object, object)
IL_0034: call void [mscorlib]System.Console::WriteLine(string)
IL_0039: nop
IL_003a: ret
} // end of method Program::Print
Здесь в строке 0009 мы сохраняем ссылку
на наш объект-строку в стек. Ничего сверхъестественного.
Вот еще один пример использования оператора nameof:
public static void
NameofTest()
{
var message = "test";
var name = nameof(message);
WriteLine(name);
WriteLine(nameof(Point));
int? n = null;
WriteLine(nameof(n));
}
Здесь на уровне IL-кода происходит то
же самое, что и в предыдущем примере.
.method public hidebysig static
void NameofTest () cil managed
{
// Method begins at RVA 0x20b0
// Code size 51 (0x33)
.maxstack 1
.locals init (
[0] string,
[1] string,
[2] valuetype [mscorlib]System.Nullable`1<int32>
)
IL_0000: nop
IL_0001: ldstr "test"
IL_0006: stloc.0
IL_0007: ldstr "message"
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: call void [mscorlib]System.Console::WriteLine(string)
IL_0013: nop
IL_0014: ldstr "Point"
IL_0019: call void [mscorlib]System.Console::WriteLine(string)
IL_001e: nop
IL_001f: ldloca.s 2
IL_0021: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0027: ldstr "n"
IL_002c: call void [mscorlib]System.Console::WriteLine(string)
IL_0031: nop
IL_0032: ret
} // end of method Program::NameofTest
void NameofTest () cil managed
{
// Method begins at RVA 0x20b0
// Code size 51 (0x33)
.maxstack 1
.locals init (
[0] string,
[1] string,
[2] valuetype [mscorlib]System.Nullable`1<int32>
)
IL_0000: nop
IL_0001: ldstr "test"
IL_0006: stloc.0
IL_0007: ldstr "message"
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: call void [mscorlib]System.Console::WriteLine(string)
IL_0013: nop
IL_0014: ldstr "Point"
IL_0019: call void [mscorlib]System.Console::WriteLine(string)
IL_001e: nop
IL_001f: ldloca.s 2
IL_0021: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0027: ldstr "n"
IL_002c: call void [mscorlib]System.Console::WriteLine(string)
IL_0031: nop
IL_0032: ret
} // end of method Program::NameofTest
Лично я считаю, что добавление этого оператора как минимум улучшит
красоту кода и избавит нас от “магических строк”, когда нужно пробросить
какое-то исключение. В других случаях применение этого оператора будет не так популярно,
как в случае обработки ошибок.
String Interpolation
Также в новый C# добавилось
немного синтаксического сахара, который позволяет скрывать функцию string.Format в более компактный
вид с помощью записи $””. Как это выглядит, можно посмотреть в примере ниже.
var point =
new Point { X = 3, Y = 5 };
var message = $"coordinate X = {point.X}, coordinate Y =
{point.Y}";
WriteLine(message);
Для C# 5 и ниже эту
запись можно переписать в следующую:
var point = new Point { X = 3, Y = 5 };
var message = string.Format("coordinate X = {0}, coordinate Y = {1}", point.X, point.Y);
Console.WriteLine(message);
На уровне IL-кода она так и
интерпретируется.
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 84 (0x54)
.maxstack 3
.entrypoint
.locals init (
[0] class NewSharpFeatures.Point,
[1] string,
[2] class NewSharpFeatures.Point
)
IL_0000: nop
IL_0001: newobj instance void NewSharpFeatures.Point::.ctor()
IL_0006: stloc.2
IL_0007: ldloc.2
IL_0008: ldc.i4.3
IL_0009: callvirt instance void NewSharpFeatures.Point::set_X(int32)
IL_000e: nop
IL_000f: ldloc.2
IL_0010: ldc.i4.5
IL_0011: callvirt instance void NewSharpFeatures.Point::set_Y(int32)
IL_0016: nop
IL_0017: ldloc.2
IL_0018: stloc.0
IL_0019: ldstr "coordinate X = {0}, coordinate Y = {1}"
IL_001e: ldloc.0
IL_001f: callvirt instance int32 NewSharpFeatures.Point::get_X()
IL_0024: box [mscorlib]System.Int32
IL_0029: ldloc.0
IL_002a: callvirt instance int32 NewSharpFeatures.Point::get_Y()
IL_002f: box [mscorlib]System.Int32
IL_0034: call string [mscorlib]System.String::Format(string, object, object)
IL_0039: stloc.1
IL_003a: ldloc.1
IL_003b: call void [mscorlib]System.Console::WriteLine(string)
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 84 (0x54)
.maxstack 3
.entrypoint
.locals init (
[0] class NewSharpFeatures.Point,
[1] string,
[2] class NewSharpFeatures.Point
)
IL_0000: nop
IL_0001: newobj instance void NewSharpFeatures.Point::.ctor()
IL_0006: stloc.2
IL_0007: ldloc.2
IL_0008: ldc.i4.3
IL_0009: callvirt instance void NewSharpFeatures.Point::set_X(int32)
IL_000e: nop
IL_000f: ldloc.2
IL_0010: ldc.i4.5
IL_0011: callvirt instance void NewSharpFeatures.Point::set_Y(int32)
IL_0016: nop
IL_0017: ldloc.2
IL_0018: stloc.0
IL_0019: ldstr "coordinate X = {0}, coordinate Y = {1}"
IL_001e: ldloc.0
IL_001f: callvirt instance int32 NewSharpFeatures.Point::get_X()
IL_0024: box [mscorlib]System.Int32
IL_0029: ldloc.0
IL_002a: callvirt instance int32 NewSharpFeatures.Point::get_Y()
IL_002f: box [mscorlib]System.Int32
IL_0034: call string [mscorlib]System.String::Format(string, object, object)
IL_0039: stloc.1
IL_003a: ldloc.1
IL_003b: call void [mscorlib]System.Console::WriteLine(string)
IL_004c: nop
IL_004d: call string [mscorlib]System.Console::ReadLine()
IL_0052: pop
IL_0053: ret
} // end of method Program::Main
IL_004d: call string [mscorlib]System.Console::ReadLine()
IL_0052: pop
IL_0053: ret
} // end of method Program::Main
Например, выше пример, в котором мы использовали оператор nameof в функции Print, можно переписать следующим образом:
public static void
Print(Point point)
{
if (point == null)
throw new ArgumentException(nameof(point));
WriteLine($"coordinate X = {point.X}, coordinate Y =
{point.Y}");
}
Интерполяция строк отлично работает для простых строк, когда нужно два
простых аргумента вывести через string.Format. А что если у нас решение намного сложнее.
Например, попробуйте приведенный ниже пример переписать через интерполяцию
строк вместо string.Format.
public static void
DisgustingStringInterpolation(DateTime? beginDate, DateTime? endDate)
{
var message = string.Format("Start date {0},
End date {1}",
beginDate.HasValue ?
beginDate.ToString() : "indefinite",
endDate.HasValue ? endDate.ToString() :
"indefinite");
WriteLine(message);
}
Если вы сможете запихнуть это в string interpolation, а потом другие
смогут без труда читать это, то вы действительно мастер. Мое субъективное
мнение по этому поводу заключается в том, что это чушь собачья. Во-первых, мы
не можем нормально весь код перенести на код с интерполяцией строк, а во-вторых, использование двух разных подходов с явным и неявным string.Format говорит о
непродуманности использования данного подхода. Такой себе синтаксический сахар, который можно использовать только в
простых случаях.
Dictionary initializer
Новый инициализатор словаря, как минимум, позволяет лучше читать понимать
написанный код, так как запись становится более читабельной и короткой.
Пример для C# 5:
public Dictionary<int,
string> _persons = new Dictionary<int,
string>
{
{1, "Aleksand"},
{2, "John"},
{3, "Petr" }
};
Пример для C# 6:
public Dictionary<int,
string> _persons = new Dictionary<int,
string>
{
[1]
= "Aleksand",
[2]
= "John",
[3]
= "Petr"
};
Согласитесь, для C# 6 пример выглядит
намного лучше для понимания и чтения, чем для старой версии. Но и здесь есть
ложка дегтя. Как написал Джон Скит, добавляя такой синтаксис, ребята из Майкрософт
допустили ошибку. Например, приведенный ниже код с синтаксисом C# 5 версии и ниже выдаст ошибку, если указать
случайно один и тот же ключ. Для 6-ой версии он работает на ура.
private static void
DictionaryInitilize()
{
var a = new Dictionary<string, string>
{
{ "a", "1" },
{ "a", "2" }
};
WriteLine($"Keys : {a.Keys.Count}, Values :
{a.Values.Count}");
var b = new Dictionary<string, string>
{
["a"] = "1" ,
["a"] = "2"
};
WriteLine($"Keys : {b.Keys.Count}, Values :
{b.Values.Count}");
}
К сожалению, я не считаю такое поведение правильным. Вопрос сразу для
знатоков: какое значение будет в ключе [“a”] для словаря b?
По сути, используя такой синтаксис, компилятор будет просто глотать ваши
ошибки, если вы дважды случайно добавите один и тот же ключ. А так как от
ошибок никто не застрахован, то мы получаем себе головную боль, чтобы проверять,
а все ли мы верно заполнили, если раньше могли узнать об этом от компилятора. Думаю, вам все-таки интересно, почему в новом C# происходит глотание ошибок. Для этого нам нужно
копнуть внутренности IL-кода, который
приоткроет для нас занавес тайны.
.method private hidebysig static
void DictionaryInitilize () cil managed
{
// Method begins at RVA 0x2194
// Code size 182 (0xb6)
.maxstack 3
.locals init (
[0] class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>,
[1] class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>,
[2] class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>
)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::.ctor()
IL_0006: stloc.2
IL_0007: ldloc.2
IL_0008: ldstr "a"
IL_000d: ldstr "1"
IL_0012: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::Add(!0, !1)
IL_0017: nop
IL_0018: ldloc.2
IL_0019: ldstr "a"
IL_001e: ldstr "2"
IL_0023: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::Add(!0, !1)
IL_0028: nop
IL_0029: ldloc.2
IL_002a: stloc.0
IL_002b: ldstr "Keys : {0}, Values : {1}"
IL_0030: ldloc.0
IL_0031: callvirt instance class [mscorlib]System.Collections.Generic.Dictionary`2/KeyCollection<!0, !1> class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::get_Keys()
IL_0036: callvirt instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2/KeyCollection<string, string>::get_Count()
IL_003b: box [mscorlib]System.Int32
IL_0040: ldloc.0
IL_0041: callvirt instance class [mscorlib]System.Collections.Generic.Dictionary`2/ValueCollection<!0, !1> class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::get_Values()
IL_0046: callvirt instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2/ValueCollection<string, string>::get_Count()
IL_004b: box [mscorlib]System.Int32
IL_0050: call string [mscorlib]System.String::Format(string, object, object)
IL_0055: call void [mscorlib]System.Console::WriteLine(string)
IL_005a: nop
IL_005b: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::.ctor()
IL_0060: stloc.2
IL_0061: ldloc.2
IL_0062: ldstr "a"
IL_0067: ldstr "1"
IL_006c: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::set_Item(!0, !1)
IL_0071: nop
IL_0072: ldloc.2
IL_0073: ldstr "a"
IL_0078: ldstr "2"
IL_007d: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::set_Item(!0, !1)
IL_0082: nop
IL_0083: ldloc.2
IL_0084: stloc.1
IL_0085: ldstr "Keys : {0}, Values : {1}"
IL_008a: ldloc.1
IL_008b: callvirt instance class [mscorlib]System.Collections.Generic.Dictionary`2/KeyCollection<!0, !1> class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::get_Keys()
IL_0090: callvirt instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2/KeyCollection<string, string>::get_Count()
IL_0095: box [mscorlib]System.Int32
IL_009a: ldloc.1
IL_009b: callvirt instance class [mscorlib]System.Collections.Generic.Dictionary`2/ValueCollection<!0, !1> class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::get_Values()
IL_00a0: callvirt instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2/ValueCollection<string, string>::get_Count()
IL_00a5: box [mscorlib]System.Int32
IL_00aa: call string [mscorlib]System.String::Format(string, object, object)
IL_00af: call void [mscorlib]System.Console::WriteLine(string)
IL_00b4: nop
IL_00b5: ret
} // end of method Program::DictionaryInitilize
void DictionaryInitilize () cil managed
{
// Method begins at RVA 0x2194
// Code size 182 (0xb6)
.maxstack 3
.locals init (
[0] class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>,
[1] class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>,
[2] class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>
)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::.ctor()
IL_0006: stloc.2
IL_0007: ldloc.2
IL_0008: ldstr "a"
IL_000d: ldstr "1"
IL_0012: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::Add(!0, !1)
IL_0017: nop
IL_0018: ldloc.2
IL_0019: ldstr "a"
IL_001e: ldstr "2"
IL_0023: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::Add(!0, !1)
IL_0028: nop
IL_0029: ldloc.2
IL_002a: stloc.0
IL_002b: ldstr "Keys : {0}, Values : {1}"
IL_0030: ldloc.0
IL_0031: callvirt instance class [mscorlib]System.Collections.Generic.Dictionary`2/KeyCollection<!0, !1> class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::get_Keys()
IL_0036: callvirt instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2/KeyCollection<string, string>::get_Count()
IL_003b: box [mscorlib]System.Int32
IL_0040: ldloc.0
IL_0041: callvirt instance class [mscorlib]System.Collections.Generic.Dictionary`2/ValueCollection<!0, !1> class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::get_Values()
IL_0046: callvirt instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2/ValueCollection<string, string>::get_Count()
IL_004b: box [mscorlib]System.Int32
IL_0050: call string [mscorlib]System.String::Format(string, object, object)
IL_0055: call void [mscorlib]System.Console::WriteLine(string)
IL_005a: nop
IL_005b: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::.ctor()
IL_0060: stloc.2
IL_0061: ldloc.2
IL_0062: ldstr "a"
IL_0067: ldstr "1"
IL_006c: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::set_Item(!0, !1)
IL_0071: nop
IL_0072: ldloc.2
IL_0073: ldstr "a"
IL_0078: ldstr "2"
IL_007d: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::set_Item(!0, !1)
IL_0082: nop
IL_0083: ldloc.2
IL_0084: stloc.1
IL_0085: ldstr "Keys : {0}, Values : {1}"
IL_008a: ldloc.1
IL_008b: callvirt instance class [mscorlib]System.Collections.Generic.Dictionary`2/KeyCollection<!0, !1> class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::get_Keys()
IL_0090: callvirt instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2/KeyCollection<string, string>::get_Count()
IL_0095: box [mscorlib]System.Int32
IL_009a: ldloc.1
IL_009b: callvirt instance class [mscorlib]System.Collections.Generic.Dictionary`2/ValueCollection<!0, !1> class [mscorlib]System.Collections.Generic.Dictionary`2<string, string>::get_Values()
IL_00a0: callvirt instance int32 class [mscorlib]System.Collections.Generic.Dictionary`2/ValueCollection<string, string>::get_Count()
IL_00a5: box [mscorlib]System.Int32
IL_00aa: call string [mscorlib]System.String::Format(string, object, object)
IL_00af: call void [mscorlib]System.Console::WriteLine(string)
IL_00b4: nop
IL_00b5: ret
} // end of method Program::DictionaryInitilize
Посмотрите внимательно на этот код. Он не так страшен, как кажется на первый взгляд. Для словаря a используется для добавления значения в словарь метод Add, который пытается вставить две записи с помощью метода Insert. Для словаря b мы же сразу пытаемся проставить нужное значение. Приведенный выше код интерпретируется так, словно мы переписали использование словаря b в следующий вид:
var b = new Dictionary<string, string>();
b["a"] = "1";
b["a"] = "2";
Как видите, мы попросту заменяем наше значение в ключе “a”.
Вы сами можете убедиться в том, что именно в такую запись компилятор перегоняет ваш код. Помните, мы в начале рассмотрели пример инициализации словаря _persons.
Посмотрите, как компилятор интерпретировал этот код.
public Dictionary<int, string> _persons;
public Program()
{
Dictionary<int, string> dictionary = new Dictionary<int, string>();
dictionary[1] = "Aleksand";
dictionary[2] = "John";
dictionary[3] = "Petr";
this._persons = dictionary;
base..ctor();
}
{
Dictionary<int, string> dictionary = new Dictionary<int, string>();
dictionary[1] = "Aleksand";
dictionary[2] = "John";
dictionary[3] = "Petr";
this._persons = dictionary;
base..ctor();
}
Теперь вы знаете, что происходит внутри и какое значение будет присвоено
ключу “a” для словаря b.
Null Propagation Operator (Null
Coalescing Operator)
Как по мне, то это самая замечательная возможность, которая появилась в C# 6-ом. По сути, у вас монада Maybe, которая записывается с помощью оператора ”?.”. Этот
оператор нужен для того чтобы обойти ситуацию, когда свойство может быть null. Ниже приведен пример, как вы писали код для более старой
версии C# и который вы наверняка
использовали с своих программах на 90%.
public event Action<int> Progress;
public void UpdateProgress(int
number)
{
if(Progress != null)
Progress(number);
}
Хотя вы могли и обходить такую ситуацию с проверкой на null через создание пустого делегата.
public event Action<int> Progress = delegate {};
public void UpdateProgress(int
number)
{
Progress(number);
}
С простыми ивентами это дело десятое. Но если вы пишете на WPF, то вы не могли обойти стороной интерфейс INotifyPropertyChanged с событием PropertyChanged. Посмотрите, если у вас есть использование PropertyChanged в котором вы его проверяете на Null, то можете забыть об этом геморрое. Сейчас
достаточно переписать приведенный выше пример с использованием null coalescing operator.
public event Action<int> Progress;
public void UpdateProgress(int
number)
{
Progress?.Invoke(number);
}
Второе место, где удобно использовать данный оператор — это метод Dispose, в котором мы зачастую проверяем, не равен ли объект Null, и если он все-таки не равен, то вызвать для этого
объекта Dispose.
Before:
public Point _point;
public void Dispose()
{
if(_point != null)
_point.Dispose();
}
After:
public Point _point;
public void Dispose()
{
_point?.Dispose();
}
Есть и другие применения данного оператора, как, например, большая
вложенность, когда каждый член может быть равен null. У меня такой код очень редко встречался поэтому
не хочется даже приводить штучный пример для него. Ну и, наверное, этот
оператор неплохо вам может пригодиться совместно с LINQ, если вы будете использовать функции, которые
возвращают какое-то дефолтное значение FirstOrDefault, SingleOrDefault и т.д.
Заключение
Мы рассмотрели примеры, которые зачастую встречаются в реальных проектах, а также показал, как та или иная особенность работает изнутри. Эта часть включает все оставшееся возможности языка C#, не описанные в таких моих предыдущих статьях, как "Expression body members and auto-properties in C# 6.0", "Filter exceptions" и "Async/await in catch/finally blocks". Все, что вас интересует, вы можете найти в предыдущих статьях. Надеюсь, что статья получилась не очень сухой и понравилась вам приведенными примерами. В целом, у меня по планах осталась одна статья, которая предполагает рассмотреть C# 6.0 с критической точки зрения и описать те или иные возможности в плане полезности. Буду благодарен за ваши комментарии по поводу изложения и подачи материала.
No comments:
Post a Comment