Нужен ли мой обходной путь для ковариантных параметров generi c? - PullRequest
2 голосов
/ 14 марта 2020

Я начал с простого универсального c интерфейса:

interface IFooContext<TObject>
{
    TObject Value { get; }

    String DoSomething<TValue>( Expression<Func<TObject,TValue>> lambdaExpression );
}

// Usage:
IFooContext<Panda> ctx = ...
String str = ctx.DoSomething( panda => panda.EatsShootsAndLeaves );

Однако мне нужно было сделать обобщенный тип c этого интерфейса ковариантным (по причинам, по которым я не go включаю), однако это вызывает ошибку компилятора, потому что Func<T0,TReturn> требует, чтобы T0 был контравариантным (in T0) или инвариантным параметром:

interface IFooContext<out TObject>
{
    TObject Value { get; }

    String DoSomething<TValue>( Expression<Func<TObject,TValue>> lambdaExpression );
}

// Intended usage:
IFooContext<Panda> ctx1 = ...
IFooContext<Ursidae> ctx2 = ctx1; // yay for covariance!
String str = ctx2.DoSomething( bear => bear.PoopsInTheWoods );

Таким образом, я получаю эту ошибку компилятора для объявления DoSomething:

Ошибка CS1961 Недопустимая дисперсия: параметр типа 'TObject' должен быть неизменно допустимым для 'IFooContext<TObject>.DoSomething<TValue>(Expression<Func<TObject, TValue>>)'. «TObject» является ковариантным.

После бросания различных идей в стену я обнаружил, что могу обойти это, переместив DoSomething в не универсальный c интерфейс и указав его параметр TObject, указанный в метод, а затем «выставить» первоначально намеченный метод как метод расширения следующим образом:

interface IFooContext
{
    String DoSomething<TObject,TValue>( Expression<Func<TObject,TValue>> lambdaExpression );
}

interface IFooContext<TObject>
{
    TObject Value { get; }
}

public static class FooContextExtensions
{
    public static String DoSomething<TObject,TValue>( this IFooContext<TObject> context, Expression<Func<TObject,TValue>> lambdaExpression )
    {
        return context.DoSomething<TObject,Value>( lambdaExpression );
    }
}

// Actual usage:
IFooContext<Panda> ctx1 = ...
IFooContext<Ursidae> ctx2 = ctx1; // yay for covariance!
String str = ctx2.DoSomething( bear => bear.PoopsInTheWoods );

И он компилируется и запускается без проблем - и синтаксис фактического использования идентичен синтаксису предполагаемого использования моего более раннего примера.

Почему это работает и почему компилятор C# не может выполнить этот трюк для меня внутренне с моим оригинальным единственным ковариантным интерфейсом generi c?

1 Ответ

0 голосов
/ 14 марта 2020

Поскольку TObject используемый методом наследуется от интерфейса. Поэтому, когда создается интерфейсный тип generi c, TObject становится фиксированным типом метода.

Например, если интерфейс построен как IFoo<Chrome>, то DoSomething будет ожидать, что Expression<Func<Chrome, TValue>> будет передано.

Преобразование интерфейса в IFoo<Browser> не изменит построенный метод выше. А передача экземпляра Browser методу, ожидающему Chrome, недопустима, поэтому компилятор выдает ошибку.

Однако, если вы переместите определение TObject в метод, он не будет быть построенным, пока метод не вызван. т.е. если вы передадите Browser в методе, построенном с использованием Browser. Таким образом, у вас нет очевидной проблемы, которую компилятор выяснит. Если ваша реализация метода ожидает Chrome в других браузерах, вы получите исключение времени выполнения.

Хотя синтаксис выглядит одинаково, скомпилированный код не скрыт.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...