почему мы предпочитаем? чтобы ?? оператор в c #? - PullRequest
39 голосов
/ 09 октября 2009

Я недавно обнаружил, что мы можем использовать ?? Оператор для проверки нулей. Пожалуйста, проверьте приведенные ниже примеры кода:

   var res = data ?? new data();

Это в точности похоже на

   var res = (data==null) ? new data() : data ;

Я проверил весь свой репозиторий исходного кода проекта и некоторые другие проекты с открытым исходным кодом. И этот ?? оператор никогда не использовался.

Мне просто интересно, есть ли какая-то причина, например, проблемы с производительностью или что-то в этом роде?

EDIT:

Я только что обновил свой пример кода, основываясь на комментариях от recursive & Anton. Это ошибка в беспечном. (

Ответы [ 7 ]

58 голосов
/ 09 октября 2009

Оператор null coalesce намного понятнее при проверке на null, что является его основной целью. Он также может быть прикован цепью.

object a = null;
object b = null;
object c = new object();
object d = a ?? b ?? c; //d == c.

Хотя этот оператор ограничен нулевой проверкой, троичный оператор - нет. Например

bool isQuestion = true;
string question = isQuestion ? "Yes" : "No";

Я думаю, что люди просто не знают об операторе слияния null, поэтому вместо них они используют троичный оператор. Ternary существовал до C # в большинстве языков стиля C, поэтому, если вы не знаете C # внутри и снаружи и / или программируете на другом языке, ternary - естественный выбор. Тем не менее, если вы проверяете на null, используйте оператор объединения null, он предназначен для этого, и IL немного оптимизирован (сравните ?? с if, если еще).

Вот пример, сравнивающий использование каждого

object a = null;
object b = null;
object c = null;

object nullCoalesce = a ?? b ?? c;

object ternary = a != null ? a : b != null ? b : c;

object ifThenElse;

if (a != null)
    ifThenElse = a;
else if (b != null)
    ifThenElse = b;
else if (c != null)
    ifThenElse = c;

Во-первых, просто посмотрите на синтаксис для null coalesce, он намного понятнее. Тернар действительно сбивает с толку. Теперь давайте посмотрим на IL

Только Null Coalesce

.entrypoint
.maxstack 2
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object nullCoalesce)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: dup 
L_000c: brtrue.s L_0015
L_000e: pop 
L_000f: ldloc.1 
L_0010: dup 
L_0011: brtrue.s L_0015
L_0013: pop 
L_0014: ldloc.2 
L_0015: stloc.3 
L_0016: ldloc.3 
L_0017: call void [mscorlib]System.Console::WriteLine(object)
L_001c: ret 

Только троичный

.entrypoint
.maxstack 2
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object ternary)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: brtrue.s L_0016
L_000d: ldloc.1 
L_000e: brtrue.s L_0013
L_0010: ldloc.2 
L_0011: br.s L_0017
L_0013: ldloc.1 
L_0014: br.s L_0017
L_0016: ldloc.0 
L_0017: stloc.3 
L_0018: ldloc.3 
L_0019: call void [mscorlib]System.Console::WriteLine(object)
L_001e: ret 

Если только тогда остальное

.entrypoint
.maxstack 1
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object ifThenElse)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: brfalse.s L_0011
L_000d: ldloc.0 
L_000e: stloc.3 
L_000f: br.s L_001a
L_0011: ldloc.1 
L_0012: brfalse.s L_0018
L_0014: ldloc.1 
L_0015: stloc.3 
L_0016: br.s L_001a
L_0018: ldloc.2 
L_0019: stloc.3 
L_001a: ldloc.3 
L_001b: call void [mscorlib]System.Console::WriteLine(object)
L_0020: ret 

ИЛ не одно из моих сильных сторон, поэтому, возможно, кто-то может отредактировать мой ответ и расширить его. Я собирался объяснить свою теорию, но я бы не стал путать себя и других. Количество LOC одинаково для всех трех, но не всем операторам IL требуется одинаковое время для выполнения.

12 голосов
/ 09 октября 2009

Оператор (также известный как оператор объединения нулей ) менее известен, чем троичный оператор, поскольку он дебютировал с .NET 2.0 и Nullable Types. Причины, по которым он не используется, вероятно, включают в себя то, что он не осознает, что он существует, или не знаком с троичным оператором.

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

6 голосов
/ 09 октября 2009

Одной из причин, по которой я могу думать, является то, что этот оператор был введен в .NET 2.0, поэтому код для .NET 1.1 не может его иметь.

Я согласен с вами, мы должны использовать это чаще.

ссылка ссылка

4 голосов
/ 09 октября 2009

На основании Боба ответа

public object nullCoalesce(object a, object b, object c)
{
    return a ?? b ?? c;
}
public object ternary(object a, object b, object c)
{
    return a != null ? a : b != null ? b : c;
}
public object ifThenElse(object a, object b, object c)
{
    if (a != null)
        return a;
    else if (b != null)
        return b;
    else
        return c;
}

... это IL из релизных сборок ...

.method public hidebysig instance object nullCoalesce(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: dup 
    L_0002: brtrue.s L_000b
    L_0004: pop 
    L_0005: ldarg.2 
    L_0006: dup 
    L_0007: brtrue.s L_000b
    L_0009: pop 
    L_000a: ldarg.3 
    L_000b: ret 
}

.method public hidebysig instance object ternary(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: brtrue.s L_000a
    L_0003: ldarg.2 
    L_0004: brtrue.s L_0008
    L_0006: ldarg.3 
    L_0007: ret 
    L_0008: ldarg.2 
    L_0009: ret 
    L_000a: ldarg.1 
    L_000b: ret 
}

.method public hidebysig instance object ifThenElse(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: brfalse.s L_0005
    L_0003: ldarg.1 
    L_0004: ret 
    L_0005: ldarg.2 
    L_0006: brfalse.s L_000a
    L_0008: ldarg.2 
    L_0009: ret 
    L_000a: ldarg.3 
    L_000b: ret 
}
2 голосов
/ 09 октября 2009

Одной из причин (как уже коснулись другие), вероятно, является неосведомленность. Это также может быть (как и в моем собственном случае) желание сократить количество подходов, позволяющих сделать подобные вещи в кодовой базе как можно меньше. Таким образом, я склонен использовать троичный оператор для всех компактных ситуаций, если условие выполняется - это делает иначе.

Например, я считаю, что следующие два утверждения довольно похожи на концептуальном уровне:

return a == null ? string.Empty : a;    
return a > 0 ? a : 0;
1 голос
/ 09 октября 2009

Я думаю, что это просто привычка других языков. НАСКОЛЬКО МНЕ ИЗВЕСТНО, ?? оператор не используется ни на каком другом языке.

0 голосов
/ 09 октября 2009

Я бы подумал, что эквивалентен

var res = data ?? data.toString();

будет

var res = (data!=null) ? data : data.toString();
...