DOBON.NETプログラミング道掲示板
(現在 過去ログ4 を表示中)

[ 最新記事及び返信フォームをトピックトップへ ]

■34349 / inTopicNo.1)  ジェネリック型インターフェースを使った変数を扱いたい
  
□投稿者/ アフロ 一般人(1回)-(2019/11/01(Fri) 11:51:54)
  • アイコン環境/言語:[C# VisualStudio2012] 
    分類:[.NET] 

    ジェネリック型指定したインターフェースを扱っていて、行き詰ってしまいました。

    public interface IRoutine
    {
      // 基本の機能
    }

    public interface IRoutine<TParent> : IRoutine
    where T : IRoutine
    {
      // 親を設定する
      T Parent { get; set; }
    }

    ClassA : IRoutine<ClassA>
    {
    }

    ClassB : IRoutine<ClassB>
    {
    }

    としたとき、
    var routines = new IRoutine<T>[] {ClassA, ClassB}; // ※
    foreach(var routine in routines)
    {
      routine.Parent = 〇〇;
    }

    のように、ClassAとClassBを同じコレクションに入れて反復処理をしたいのですが、※の部分でうまく行きません。
    このような場合、変数の型指定はどのようにしてやれば良いのでしょうか?
    よろしくお願いします。

引用返信 削除キー/
■34351 / inTopicNo.2)  Re[1]: ジェネリック型インターフェースを使った変数を扱いたい
□投稿者/ 魔界の仮面弁士 大御所(1246回)-(2019/11/01(Fri) 16:13:16)
  • アイコンNo34349に返信(アフロさんの記事)
    > public interface IRoutine<TParent> : IRoutine
    >     where T : IRoutine
    > {
    >   // 親を設定する
    >   T Parent { get; set; }
    > }

    このインターフェイスは、<T> を持つジェネリック クラスの
    子階層で定義されているということでしょうか?
    それともこれは誤記で、実際は T = TParent なのでしょうか?


    > ClassA : IRoutine<ClassA>
    これは「class ClassA : IRoutine<ClassA>」のことでしょうか?


    > var routines = new IRoutine<T>[] {ClassA, ClassB}; // ※

    元ソースが文法的に不正確である点はさておき、
     var t0 = typeof(IRoutine);
     var t1 = typeof(IRoutine<>);
     var t2 = typeof(IRoutine<ClassA>);
     var t3 = typeof(ClassA);
    において、
     t0.IsAssignableFrom(*) は、t0〜t3 いずれに対しても true
     t1.IsAssignableFrom(*) は、t1 自身以外はすべて false
     t2.IsAssignableFrom(*) は、t0〜t1 は false、t2 自身と t3 は true
     t3.IsAssignableFrom(*) は、t3 自身以外はすべて false
    という関係性を持ちます。


    ゆえに今回の型定義の場合、書くとしても
     var routines = new IRoutine[] { new ClassA(), new ClassB() };
     foreach (dynamic routine in routines)
     {
       routine.Parent = afro;
     }
    とするしかないでしょう。


    ただしこの場合、公開された
     class ClassA : IRoutine<ClassA>
     {
       public ClassA Parent { set; get; }
     }
    のような実装が対象であり、明示実装された
     class ClassA : IRoutine<ClassA>
     {
       ClassA IRoutine<ClassA>.Parent { set; get; }
     }
    なクラスに対しては使えません。
引用返信 削除キー/
■34352 / inTopicNo.3)  Re[2]: ジェネリック型インターフェースを使った変数を扱いたい
□投稿者/ アフロ 一般人(2回)-(2019/11/01(Fri) 17:47:28)
  • アイコン魔界の仮面弁士さん
    ご回答ありがとうございます。

    >>public interface IRoutine<TParent> : IRoutine
    >>    where T : IRoutine
    すみません。TはTParentの誤りです。

    >>ClassA : IRoutine<ClassA>
    > これは「class ClassA : IRoutine<ClassA>」のことでしょうか?
    仰る通り、class ClassA : IRoutine<ClassA>のつもりでした…

    dynamicという宣言の仕方を知らなかったので、そんな方法があったかと驚きました。
    是非取り入れてみたいと思います。
    助かりました。

    しかし、インターフェースは反復処理で力を発揮するものと思っていましたが
    ジェネリックにするとそうでもないのですかね… 今更な嘆きですが…

解決み!
引用返信 削除キー/
■34353 / inTopicNo.4)  Re[3]: ジェネリック型インターフェースを使った変数を扱いたい
□投稿者/ 魔界の仮面弁士 大御所(1247回)-(2019/11/01(Fri) 18:37:58)
  • アイコン2019/11/03(Sun) 15:28:57 編集(投稿者)

    No34352に返信(アフロさんの記事)
    > dynamicという宣言の仕方を知らなかったので、そんな方法があったかと驚きました。

    ジェネリックはコンパイル時点で型が確定していますが、
    dynamic の場合は実行時に動的に判定されます。

    そのため、ジェネリックに比べるとパフォーマンスは幾許か落ちますし、
    スペルミスや型の取り違いなどがあった場合も、
    コンパイル時点では検出されません。御注意ください。


    > しかし、インターフェースは反復処理で力を発揮するものと思っていましたが
    > ジェネリックにするとそうでもないのですかね…

    No34349 にあるような型定義だと使えませんが、ジェネリックの指定如何によっては
    共変性・反変性によって対処できることはありそうです。
    https://ufcpp.net/study/csharp/sp4_variance.html

    また、将来的には共変戻り値を使えるようになるかもしれません。
    ただし仮に実装されたとしても、.NET Framework では利用できず、
    .NET 5 あるいは .NET 6 以降になりそうです。
    https://ufcpp.net/blog/2019/10/pickuproslyn1004/



    > 今更な嘆きですが…
    今回の型パラメータ T が指し示す型は、
     interface IRoutine<T> : IRoutine where T : IRoutine
     {
      T Parent { get; set; }
     }
    と定義されています。

    なので、Parent プロパティが取り扱うインスタンスは、「少なくとも IRoutine 型を持つ」ことは間違いありません。

    その上で、実際のインスタンスについては、型定義が
     「class ClassA : IRoutine<ClassA>」
     「class ClassB : IRoutine<ClassB>」
    となっています。

    ClassA と ClassB は互いの継承関係を持ちません。
    両者の共通点は、「ジェネリックではない IRoutine インターフェイスを持つこと」だけです。

    この共通している IRoutine 型を使うことで、少なくとも
     『foreach (IRoutine routine in routines)』
    と書くことはできるでしょう。

    しかし IRoutine に Parent プロパティは無いので、
    このループ変数では、Parent に代入できません。

    型の確定した Parent を持つのは IRoutine ではなく、
    IRoutine<ClassA> や IRoutine<ClassB> 、
    あるいは ClassA や ClassB 型といった固有の型だけです。

    両者に共通した型が存在していない以上、このままでは
    ループ処理させることができないというわけです。
    (先述したように、dynamic やリフレクションを用いて、
    コンパイル時点ではなく実行時に解決させることならば可能)


    逆に言えば、これはつまり ClassA と ClassB の両方に対して
    「Parent プロパティを持つ共通の型」を継承もしくは実装しておくことで、
    foreach による取りまとめが可能になることを意味します。


    たとえば、こういう書き方をすることはできるでしょう。

    // 下記 2 つの定義はそのまま
    public interface IRoutine { /* 基本となる各種メンバー定義 */ }
    public interface IRoutine<T> : IRoutine where T : IRoutine
    {
      // Parent プロパティだけを定義
      T Parent { get; set; }
    }

    // 「Parent プロパティを持つ共通の型」を新たに定義
    abstract class RoutineBase : IRoutine<IRoutine>
    {
      // abstract または virtaual で定義
      public abstract IRoutine Parent { get; set; }
    }

    // 少なくとも「共通の型」を持つように実装する
    class ClassC : RoutineBase { … }
    class ClassD : RoutineBase, IRoutine<ClassD> { … }

    ***************

    var routines = new RoutineBase[] { new ClassC(), new ClassD() };
    foreach (RoutineBase routine in routines)
    {
      routine.Parent = IRoutine型の何か;
    }

    ===================

    上記では、「共通の型」として抽象クラスを設けていますが、
    もちろんインターフェイスのままでも OK です。


    // 「Parent プロパティを持つ共通の型」の定義
    interface IParent : IRoutine<IRoutine> { }


    // 少なくとも「共通の型」を持つように実装する
    class ClassE : IParent {
     public IRoutine Parent { get; set; }
    }
    class ClassF : IParent, IRoutine<ClassF> { … }

    ***************
    var routines = new IParent[] { new ClassE(), new ClassF() };
    foreach (IParent routine in routines)
    {
      routine.Parent = IRoutine型の何か;
    }


    あるいは、そもそも IRoutine<T> を廃止してしまい、上記 IParent を
     interface IParent : IRoutine
     {
       IRoutine Parent { get; set; }
     }
    にしてしまうという手もあります。
解決み!
引用返信 削除キー/



トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

Mode/  Pass/


- Child Tree -