C # ковариация по общим методам - PullRequest
0 голосов
/ 28 января 2019

В Java я могу легко написать:

interface A { void foo(); }
class B implements A { public void foo() {} }
class C implements A { public void foo() {} }

<T> List<T> combined(Collection<? extends T> a, Collection<? extends T> b) {
    List<T> res = new ArrayList<>();
    res.addAll(a);
    res.addAll(b);
    return res;
}

public static void main(String[] args) {
    List<A> list = combine(Arrays.asList(new B()), Arrays.asList(new C()));
}

Приведенный выше код компилируется, когда помещается в класс (игнорируя модификаторы и т. Д.).

Цель состоит в том, чтобы написать функцию Combineкоторый берет два IEnumerable с потенциально различных типов A и B, которые оба наследуются от некоторого T. Компилятор должен вывести тип T и вернуть IEnumerable<T> без моего участияявно указать любой тип в вызове функции.

Как бы я сделал это в C #?

Ответы [ 3 ]

0 голосов
/ 28 января 2019

Нет, алгоритм вывода типов компилятора не пойдет в гору в поисках общего базового типа, и его нельзя применять только к параметрам универсального типа.Это не будет работать либо:

IFoo foo = condition ? b : c;

Почему?Ну, потому что не всегда ясно, каким должен быть базовый тип, и это может быть источником ошибок.Как компилятор знает, где остановиться?В конце концов, все может быть сведено к общему типу object.

Кроме того, следует ли учитывать определенные пользователем преобразования?И следует ли рассматривать общие базовые типы после каждого возможного разговора?Это может продолжаться вечно.Просто гораздо лучше, если вы явно указали, какой тип вам нужен, и компилятор не угадал, что вы хотели.

Правило состоит в том, что алгоритм вывода типов будет использовать в качестве подходящих кандидатов только те типы, которые явно используютсяв выражении.Если среди этих кандидатов не найден действительный тип, тогда вывод просто не удастся.

В предыдущем примере решение, конечно, следующее:

IFoo foo = condition ? (IFoo)b : c;

И в вашем случае решениене полагаясь на алгоритм вывода, вам необходимо явно объявить требуемый параметр универсального типа.

После комментариев, способ, которым C # обрабатывает это, решает непреднамеренные критические изменения:

class A: IFoo
class B: IFoo, IBar

Потребитель пишетнекоторый код:

var foo = someCondition ? a : b //asume this is legal and resolves type to IFoo

Теперь кто-то думает, что пришло время обновить A до A: IFoo, IBar, и вдруг потребитель ломается.

0 голосов
/ 28 января 2019

Это работает, если вы не полагаетесь на вывод типа и полностью указываете все типы в функции:

class CommonInterfaces
{
    public static void DoWork()
    {
        var lst1 = new List<A>();
        var lst2 = new List<B>();
        var result = Merge<ICommon, A, B>(lst1, lst2);

    }

    public static IEnumerable<T> Merge<T, TA, TB>(IEnumerable<TA> a, IEnumerable<TB> b) where TA: T where TB: T
    {
        foreach (var itm in a)
            yield return itm;
        foreach (var itm in b)
            yield return itm;
    }
}

public interface ICommon { }
public class A : ICommon { }
public class B : ICommon { }

Спасибо @Igor за большую часть набора текста на этом

0 голосов
/ 28 января 2019

Вы можете сделать то же самое в c #, см. Ниже.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        var lst1 = new List<A>();
        var lst2 = new List<B>();
        var result = Merge<ICommon>(lst1, lst2);
    }

    public static IEnumerable<T> Merge<T>(IEnumerable<T> a, IEnumerable<T> b){
        foreach(var itm in a)
            yield return itm;
        foreach(var itm in b)
            yield return itm;
    }
}

public interface ICommon {}
public class A : ICommon{}
public class B : ICommon{}

Компилятор должен вывести тип T и вернуть IEnumerable<T> без необходимости явного указаниялюбой тип в вызове функции.

Это невозможно, вам придется указать общий тип для T.

...