Подтвердить что ты не робот

Оператор Null-распространения и методы расширения

Я смотрел Visual C 14 CTP вместе с С# 6.0 и играл с оператором распространения пустоты.

Однако я не мог найти, почему следующий код не компилируется. Функции еще не задокументированы, поэтому я не уверен, что это ошибка или методы расширения просто не поддерживаются оператором ?., а сообщение об ошибке вводит в заблуждение.

class C
{
    public object Get()
    {
        return null;
    }
}

class CC
{
}

static class CCExtensions
{
    public static object Get(this CC c)
    {
        return null;
    }
}

class Program
{
    static void Main(string[] args)
    {
        C c = null;
        var cr = c?.Get();   //this compiles (Get is instance method)

        CC cc = null;
        var ccr = cc?.Get(); //this doesn't compile

        Console.ReadLine();
    }
}

Сообщение об ошибке:

"ConsoleApplication1.CC" не содержит определения для "Get" и не может быть найден метод расширения "Get", принимающий первый аргумент типа "ConsoleApplication1.CC" (вам не хватает директивы using или ссылки на сборку? )

4b9b3361

Ответ 1

Я не работаю над командой Roslyn, но я уверен, что это ошибка. Я взглянул на исходный код, и я могу объяснить, что происходит.

Во-первых, я не согласен с SLaks ответить, что это не поддерживается, потому что методы расширения не разыгрывают их параметр this. Это необоснованное утверждение, учитывая, что в нем не упоминается ни один из дизайн discussions. Кроме того, семантика оператора превращается в нечто примерно похожее на тернарный оператор ((obj == null) ? null : obj.Member), поэтому на самом деле нет веской причины, почему его нельзя поддерживать в техническом смысле. Я имею в виду, что когда он сводится к сгенерированному коду, действительно нет никакой разницы в неявном this на методе экземпляра и явном this на статическом методе расширения.

Сообщение об ошибке - это хорошая подсказка, что это ошибка, потому что она жалуется, что метод не существует, когда он это делает. Возможно, вы протестировали это, удалив условный оператор из вызова, используя вместо этого оператор доступа к члену и успешно скомпилировав код. Если это было незаконным использованием оператора, вы получите сообщение, подобное этому: error CS0023: Operator '.' cannot be applied to operand of type '<type>'.

Ошибка заключается в том, что когда Binder пытается привязать синтаксис к скомпилированным символам, он использует метод private static NameSyntax GetNameSyntax(CSharpSyntaxNode, out string) [link], который не возвращает имя метода, которое необходимо, когда оно пытается связать выражение вызова (наш вызов метода).

Возможное исправление заключается в добавлении дополнительной инструкции case к коммутатору в GetNameSyntax [link] следующим образом (Файл: Компиляторы /CSharp/Source/Binder/Binder _Expressions.cs: 2748):

// ...
case SyntaxKind.MemberBindingExpression:
     return ((MemberBindingExpressionSyntax)syntax).Name;
// ...

Это, вероятно, было упущено, потому что синтаксис для вызова методов расширения как членов, использующий оператор доступа к члену) завершается с использованием другого набора синтаксиса, чем при использовании с оператором доступа к члену и оператору условного доступа, оператор ?. использует MemberBindingExpressionSyntax, который не был учтен для этого метода GetNameSyntax.

Интересно, что имя метода не заполняется для первого var cr = c?.Get();, который компилируется. Он работает, однако, потому что локальные члены группы методов сначала найдены для типа и передаются на вызов BindInvocationExpression [ссылка]. Когда метод устраняется (обратите внимание на вызов ResolveDefaultMethodGroup [link] перед тем, как попробовать BindExtensionMethod [ссылка], он сначала проверяет эти методы и находит их. В случае метода расширения он пытается найти метод расширения, который соответствует имени метода, который был передан в метод, который в этом случае был пустой строкой вместо Get, и вызывает отображение ошибочной ошибки.

С моей локальной версией Roslyn с исправлением ошибки я получаю скомпилированную сборку, код которой выглядит (регенерируется с помощью dotPeek):

internal class Program
{
    private static void Main(string[] args)
    {
        C c1 = (C) null;
        object obj1 = c1 != null ? c1.Get() : (object) null;
        CC c2 = (CC) null;
        object obj2 = c2 != null ? CCExtensions.Get(c2) : (object) null;
        Console.ReadLine();
    }
}

Ответ 2

Да. Это ошибка. Спасибо, что вызвали это. Образец должен компилироваться и должен приводить к условному вызову Get независимо от того, является ли Get расширением или нет.

Использование "?". в cc?.Get() является признаком того, что вызывающий объект хочет cc null-checked перед продолжением. Даже если Get может каким-то образом обработать null, вызывающий не хочет, чтобы это произошло.

Ответ 3

Точкой оператора нулевого распространения является исключение детекции значения null.

Однако методы расширения не отдают предпочтение их параметру this, и на самом деле его можно назвать совершенно точным на значениях null.
(хотя сам метод, вероятно, генерирует исключение, если он не ожидает null)

Следовательно, было бы непонятно, будет ли вызов с нулевым безопасным вызовом пропустить вызов или нет.