1

Тема: Як створити змінну типу об'єднання кількох інтерфейсів ? (Вирішено)

Є бібліотека стороннього розробника, в тій бібліотеці є класи та інтерфейси, наприклад:

namespace SomeSpace
{
    public interface I1
    {
        void f1();
    }
    public interface I2
    {
        void f2();
    }
    public class C : I1, I2 //, ...
    {
        public void f1() { }  // implement I1.f1();
        public void f2() { }  // implement I2.f2();
        public void f3() { }  // implement other interface member.
        // ...
    }
}

Я підключив цю бібліотеку до свого проекту і створив екземпляр класу C, далі я хочу помістити (присвоїти) цей екземпляр в змінну такого типу, який дозволить викликати (через крапку) будь-які властивості і методи інтерфейсів I1, I2, і не дозволить викликати інші властивості і методи класу C.
В ідеалі я б хотів, щоб це виглядало як в наступному прикладі в стрічках перед "// Error N", де N - це цифра.

using SomeSpace

namespace MySpace
{
    public interface I12 : I1, I2
    { }
    public class Program
    {
        public static void Main(string[] args)
        {
            C c = new C();
            I1 i1 = c;       // OK
            I2 i2 = c;       // OK

        //  I12 i = c;       // Error 1
        //  I1, I2 i = c;    // Error 2
        //  {I1, I2} i = c;  // Error 3

            i.f1();  // OK
            i.f2();  // OK
        //  i.f3();  // Error here is OK
        }
    }
}

Звичайно, що Visual Studio на таке свариться (якщо розкоментувати рядки з Error 1, 2, 3), каже щоб я йшов туди, куди сам часто посилаю, на офіційну документацію по синтаксису.
Я знаю, що це можна реалізувати наступним способом:

using SomeSpace

namespace MySpace
{
    public interface I12 : I1, I2
    { }
    public class CC<T> : I12
        where T : I1, I2
    {
        private T _t;
        public CC(T t) { _t = t; }
        public void f1() { _t.f1(); }  // implement I1.f1();
        public void f2() { _t.f2(); }  // implement I2.f2();
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            C c = new C();
            I1 i1 = c;       // OK
            I2 i2 = c;       // OK

            I12 i = new CC<C>(c);  // OK

            i.f1();  // OK
            i.f2();  // OK
        //  i.f3();  // Error here is OK
        }
    }
}

Але тепер уявіть собі, що насправді інтерфейси мають не один метод, а від 20 до 50 методів. Це означає, що я мушу написати від 40 до 100 стрічок коду тільки для того, щоб досягнути поставленої цілі, і це тільки в одному класі. Дибілізм же. Гарного рішення я не знайшов.

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

2

Re: Як створити змінну типу об'єднання кількох інтерфейсів ? (Вирішено)

дуже не вчитувався, але може type union отой мона заюзати?
https://github.com/dotnet/csharplang/issues/399

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

3

Re: Як створити змінну типу об'єднання кількох інтерфейсів ? (Вирішено)

FakiNyan написав:

дуже не вчитувався, але може type union отой мона заюзати?
https://github.com/dotnet/csharplang/issues/399

Автор запитання там пише, що є об'єднання і перетини типів в TypeScript і питає, чи є щось таке в C#.
І це якраз те що мені потрібно.
Але там йому відповіли, що в C# такого нема. Ніби то тому, що C# строго типізований, але це аж ніяк не перешкода. Реальна перешкода - це MSIL.
Висновок: MSIL -гавно, C# - гавно, і я - гавно. Треба створити свою мову програмування.
Що ж, відповідь я отримав. Дякую.

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

4 Востаннє редагувалося koala (23.11.2017 23:49:50)

Re: Як створити змінну типу об'єднання кількох інтерфейсів ? (Вирішено)

Повністю рішення, швидше за все, бути не може, інакше воно б вже десь було описане :)
Але можна зробити пару спроб якось розрулити.
1. Тримати одразу два посилання на об'єкт як на I1 і на I2. На жаль, є ризик втрати цілісності, але виглядає найпродуктивніше.
2. Погратися із вказівниками в unsafe. Не певен що щось вийде, але подумати в цей бік можна.
3. Інкапсулювати варіант (1):

public class CC
{
  public I1 as_i1() { return (I1)c; }
  public I2 as_i2() { return (I2)c; }
  private C c;
  //і ще конструктор
}
CC i = new CC(...);
i.as_i1().f1();
i.as_i2().f2();
//i.f3(); - помилка
//i.as_c().f3(); - помилка

Так, i.as_I1() писати довше за просто i, зате працює.
4. Ще можна подивитися на operator перетворення типів, але він, схоже, не вміє повертати інтерфейси.

Можете пояснити, чому виникло питання? Може, ви щось взагалі не так робите. Чому треба маскувати методи C? А може, просто бібліотека крива... Якийсь конкретний приклад.

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

5

Re: Як створити змінну типу об'єднання кількох інтерфейсів ? (Вирішено)

public interface I12 : I1, I2{ 
}
public class CC:I12,C{
}
I12 c=new CC();
Подякували: leofun01, koala2

6

Re: Як створити змінну типу об'єднання кількох інтерфейсів ? (Вирішено)

koala написав:

1. Тримати одразу два посилання на об'єкт як на I1 і на I2. На жаль, є ризик втрати цілісності, але виглядає найпродуктивніше.

Можна як варіант, але це вже буде не те що мені потрібно. Потрібен саме один об'єкт, бо потім я буду його повертати зі своєї функції.

koala написав:

2. Погратися із вказівниками в unsafe. Не певен що щось вийде, але подумати в цей бік можна.

Попробував зробити через конвертацію вказівників і компілятор навіть схавав. Але на етапі виконання вилетів ексепшн і прокляв мене до третього коліна.

koala написав:

3. Інкапсулювати варіант (1):

public class CC
{
  public I1 as_i1() { return (I1)c; }
  public I2 as_i2() { return (I2)c; }
  private C c;
  //і ще конструктор
}
CC i = new CC(...);
i.as_i1().f1();
i.as_i2().f2();
//i.f3(); - помилка
//i.as_c().f3(); - помилка

Так, i.as_I1() писати довше за просто i, зате працює.

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

koala написав:

4. Ще можна подивитися на operator перетворення типів, але він, схоже, не вміє повертати інтерфейси.

Вже пробубвав explicit перетворення

I12 i = (I12)c;    // Runtime Error

і компілятор cхавав, але був ексепшн на етапі виконання.

koala написав:

Можете пояснити, чому виникло питання? Може, ви щось взагалі не так робите. Чому треба маскувати методи C? А може, просто бібліотека крива... Якийсь конкретний приклад.

Я боявся, що доведеться відповідати на це питання.
Ідея приховування частини методів в C# прийшла після довгої роботи з C++, там можна було зробити private наслідування одного класу і public наслідування ще кількох класів, які були предками одного класу (того самого). І взагалі в C++ завжди було кілька рішень для будь-якої проблеми, хоть і через костилі, але то вже інша тема.

- Нащо їх маскувати ?
Бо ті, хто будуть використовувати мій код, повинні отримувати об'єкт, який не буде крашити всю програму. Якщо вони отримають об'єкт оригінального класу, то є такий ризик.
Майже впевнений, що бібліотека крива, але точно не скажу, бо source'ів не бачив.

А ще в C# нема friend методів і іноді це страшенно бісить.

7

Re: Як створити змінну типу об'єднання кількох інтерфейсів ? (Вирішено)

yooll написав:
public interface I12 : I1, I2{ 
}
public class CC:I12,C{
}
I12 c=new CC();

Це геніально. Воно працює, саме так як треба.
Тільки там в наслідуванні спочатку клас, а потім інтерфейс:

public interface I12 : I1, I2 { }
public class CC : C, I12 { }
I12 c = new CC();
Подякували: koala1