1

Тема: Страшна проблема з наслідуванням інтерфейсів (generic)

Досить давно програмую на C#, але тільки недавно помітив таку цікаву штуку:
C# v3.0 (принаймні в VisualStudio) не підтримує множинне наслідування однакових generic-інтерфейсів з різними типами-параметрами (на Java така сама біда твориться).

Наприклад наступний код не компілюється:

public interface MyInterface<T>
{
    T GetData();
    void SetData(T data);
}
public class MyClass<T1, T2> : MyInterface<T1>, MyInterface<T2>
{
    private T1 _t1;
    private T2 _t2;

    public T1 GetData() { return _t1; }
    public void SetData(T1 data) { _t1 = data; }

    T2 MyInterface<T2>.GetData() { return _t2; }
    public void SetData(T2 data) { _t2 = data; }
}

Пише, що не можна наслідувати обидва інтерфейси, бо T1 може бути таким як T2.

Ну це ще можна би було зрозуміти, але я спробував наступний код:

public class C1 { }
public class C2 { }
public interface MyInterface<T>
{
    T GetData();
    void SetData(T data);
}
public class MyClass<T1, T2> : MyInterface<T1>, MyInterface<T2>
    where T1 : C1
    where T2 : C2
{
    private T1 _t1;
    private T2 _t2;

    public T1 GetData() { return _t1; }
    public void SetData(T1 data) { _t1 = data; }

    T2 MyInterface<T2>.GetData() { return _t2; }
    public void SetData(T2 data) { _t2 = data; }
}

і він теж не компілюється (виводить таке ж повідомлення як і перед тим).

Але я вирішив не здаватися і попробував до першого варіанту дописати допоміжний клас:

public interface MyInterface<T>
{
    T GetData();
    void SetData(T data);
}
public class Helper<T> : MyInterface<T>
{
    private T _t1;

    public T GetData() { return _t1; }
    public void SetData(T data) { _t1 = data; }
}
public class MyClass<T1, T2> : Helper<T1>, MyInterface<T2>
{
    private T2 _t2;

    T2 MyInterface<T2>.GetData() { return _t2; }
    public void SetData(T2 data) { _t2 = data; }
}

І тут сталося диво ...
З допоміжним класом все запрацювало.
Скажіть мені хто-небудь, чому таке наслідування (як на початку) не дозволяється ?
Чому останній варіант - норм, а перший ні ?

Адже по суті MyClass і в першому і в останньому випадках однаковий.
Чим керувалися розробники мов програмування ?
Можливо ви теж зіштовхувалися з такою проблемою, напишіть як ви її вирішували.

Подякували: koala, Wolf.dp2

2 Востаннє редагувалося koala (08.05.2014 18:49:09)

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

http://stackoverflow.com/questions/1531 … -in-cs0695

From C# 5.0 Language Specification
13.4.2 Uniqueness of implemented interfaces

The interfaces implemented by a generic type declaration must remain unique for all possible constructed types. Without this rule, it would be impossible to determine the correct method to call for certain constructed types. For example, suppose a generic class declaration were permitted to be written as follows:

interface I<T>
{
    void F();
}
class X<U,V>: I<U>, I<V>
{
    void I<U>.F() {...}
    void I<V>.F() {...}
}

Were this permitted, it would be impossible to determine which code to execute in the following case:

I<int> x = new X<int,int>();
x.F();

Перекладаю: якщо ви успадковуєте два рази один інтерфейс-дженерік, то можете створити два рази функцію з однаковою сигнатурою, для яких потім буде неможливо визначити, яку саме з них викликати.
Я чесно зізнаюся, що з таким не стикався; як би виходив - залежить від конкретної ситуації, предметної області. Можете сказати, що це за класи і інтерфейси чи хоча б навести аналогічний приклад?

P.S. Якби ви одразу навели код і текст помилки, це б зекономило 3-4 пошукових запити, копіювання коду на ideone і 5 хвилин часу.

Подякували: Wolf.dp, leofun012

3

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

Припустимо ми дозволемо таке спадкування. Пишемо доволі простий інтерфейс та двічи спадкуємо на новий класс:

interface IPrint<T>
{ void Execute(); }

class Printer<T1, T2> : IPrint<T1>, IPrint<T2>
{
  public void Execute<T1>() { Console.WritLine("Nya!"); }
  public void Execute<T1>() { throw new Exception("Nya!"); }
}

Тепер ми створюємо новий екземпляр класу с однаковими дженеріками і викликаємо метод Execute.

new Printer<object, object>().Execute()

Що повинно статися? Надрукуватися слово, чи з'явитися виключення? Приведення до якогось конкретного інтерфейсу не допоможе -- вони абсолютно однакові. Ось із-за цієї невизначенності і прибрали можливість дулювати інтервейс в середені одного классу/інтерфейсу при спадкувані.

Тепер по "працюючому" прикладу:

Helper<T1> та MyInterface<T2> -- це різні інтерфейси, котрі мають спільні назви методів. Тому напряму звернутися до методів  T GetData(); void SetData(T data); не вдасться. Для цього доведеться привести MyClass до одного з інтерфейсів, наприклад....

((MyInterface<object>)MyClass<object, object>).GetData();
Подякували: leofun011

4

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

P.S. так і знав, що доки друкуватиму, хтось знайде в пошуковій системі >_<

Подякували: leofun011

5 Востаннє редагувалося leofun01 (08.05.2014 20:29:18)

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

koala написав:

якщо ви успадковуєте два рази один інтерфейс-дженерік, то можете створити два рази функцію з однаковою сигнатурою, для яких потім буде неможливо визначити, яку саме з них викликати.

C# не дозволяє реалізовувати два (або більше) публічних методи з однаковою сигнатурою, але цілком дозволяє реалізувати всі методи інтерфейсів так, що якщо сигнатура повторюється, то метод можна реалізувати БЕЗ "public", тобто по замовчуванню вони будуть "private" (хоча це слово в таких випадках теж не ставлять, як в моїх прикладах).
Це і є вирішальним у питанні "який метод використовувати ?", тобто виконуватиметься саме публічний метод (який завжди буде лише один, бо всі решта невидимі - private, принаймні до приведення типів).
Не бачу причин забороняти таке наслідування.

6

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

Еее... тобто ви хочете сказати, що на C# можна зробити

class X
{
  public int f();
  private int f();
}

і це компілюватиметься?

Подякували: leofun011

7

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

koala написав:

і це компілюватиметься?

ні, не зовсім так.
Клас обов'язково мусить реалізувати інтерфейс і private тоді взагалі не пишеться.
Наприклад:

public interface MyInterface<T>
{
    T GetData();
}
public class MyClass<T> : MyInterface<T>
{
    private T _t;
    public T GetData() { return _t; }
    T MyInterface<T>.GetData() { return default(T); }
}

8 Востаннє редагувалося leofun01 (08.05.2014 20:50:03)

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

Wolf.dp написав:

напряму звернутися до методів  T GetData(); void SetData(T data); не вдасться. Для цього доведеться привести MyClass до одного з інтерфейсів

Насправді, "void SetData(T data)" буде видимий, тобто будуть доступні:
T1 GetData();
void SetData(T1 data);
void SetData(T2 data);

9 Востаннє редагувалося leofun01 (08.05.2014 20:55:57)

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

koala написав:

Якби ви одразу навели код і текст помилки, це б зекономило 3-4 пошукових запити, копіювання коду на ideone і 5 хвилин часу.

Ось реальні приклади, де мені довелося використати "подвійне наслідування"

Post's attachments

LeoLibProj.rar 10.25 kb, 200 downloads since 2014-05-08 

10 Востаннє редагувалося koala (08.05.2014 21:23:09)

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

leofun01 написав:

Наприклад:

Де в цьому "прикладі"

leofun01 написав:

сигнатура повторюється

га? Проблема ж саме в цьому.

leofun01 написав:

Ось реальні приклади

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

Подякували: leofun011

11

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

koala написав:

Де в цьому "прикладі"

Я мав на увазі саме "public T GetData()" і "T MyInterface<T>.GetData()", вони фактично мають однакову сигнатуру, але один метод (перший) реалізований явно а всі інші (другий) - неявно.

koala написав:

код реалізує якесь завдання

Моїм завданням є створення структур (класів, екземпляри яких формуватимуть такі структури), які дуже схожі на ланцюжки (в архіві Mono...) і графи (Multi...), але на відміну від аналогічних типів, які можна знайти в різних бібліотеках (наприклад LinkedList, або Graph), мої лінкери не є контейнерами, вони є відкритими структурами, які дозволяють створювати графи.
І потрібно було в циклах проходитись як по елементах графа, так і по даних, які містилися в кожному елементі. Для цього потрібно було зробити, щоб клас реалізував IEnumerable<TClone> (де TClone - тип даного класу) і IEnumerable<TData> (TData - тип вмістимих даних), тобто це мало виглядати так:

public class MonoLinker<TData, TClone>
    : DataLinker<TData, TClone, TClone>, IList<TData>, IList<TClone>
    where TClone : MonoLinker<TData, TClone>
{ /*...*/ }

Але так не можна, тому зробив так:

public class MonoLinker<TData, TClone>
    : MonoEnumerable<TData, TClone>, IList<TData>
    where TClone : MonoLinker<TData, TClone>
{ /*...*/ }
public abstract class MonoEnumerable<TData, TClone>
    : DataLinker<TData, TClone, TClone>, IList<TClone>
    where TClone : MonoEnumerable<TData, TClone>
{ /*...*/ }

12

Re: Страшна проблема з наслідуванням інтерфейсів (generic)

Тут питання в однозначності виклику. Метод GetData з вашого прикладу має загальний доступ (public); але оскільки функції не є віртуальними, це не створює проблем для визначення, яку саме з них викликати (наприклад).

Подякували: leofun011