none
互いに所有する2個のクラスをジェネリックで定義したい RRS feed

  • 質問

  • C#のジェネリックについてお教えください。

    2個のジェネリックのクラスがあり、互いのインスタンスをプロパティで保持させたいです。
    ただし、互いの型を型パラメータで指定させたいです。
    具体的には以下のようなのですが、コンパイルが通りません。
    何かうまい実装方法がありましたら、お教えください。

    ■例
    Parentクラス、Childクラス を次のようにジェネリックで定義したいです。

     ・ParentはChildの型を型パラメータで指定する。
     ・ChildはParentの型を型パラメータで指定する。

    ここで、型パラメータに条件を設定して、互いのクラス、および派生クラス以外の
    インスタンスは保持させないようにしたいです。


    public class Parent<TChild>
        where TChild : Child<Parent<TChild>>
    {
        public TChild Child {get; set;}
    }

    public class Child<TParent>
        where TParent : Parent<Child<TParent>>
    {
        public TParent parent {get; set;}
    }


    ■コンパイルエラー

    エラー 1 型 'ConsoleApplication1.Impl.Parent<TChild>' はジェネリック型またはメソッド 'ConsoleApplication1.Impl.Child<TParent>' 内で型パラメーター 'TParent' として使用できません。'ConsoleApplication1.Impl.Parent<TChild>' から 'ConsoleApplication1.Impl.Parent<ConsoleApplication1.Impl.Child<ConsoleApplication1.Impl.Parent<TChild>>>' への暗黙的な参照変換がありません。 E:\kazuhiro\Software\test\WPF\Behavior\ConsoleApplication1\ConsoleApplication1\Impl\Parent.cs 9 18 ConsoleApplication1

    エラー 2 型 'ConsoleApplication1.Impl.Child<TParent>' はジェネリック型またはメソッド 'ConsoleApplication1.Impl.Parent<TChild>' 内で型パラメーター 'TChild' として使用できません。'ConsoleApplication1.Impl.Child<TParent>' から 'ConsoleApplication1.Impl.Child<ConsoleApplication1.Impl.Parent<ConsoleApplication1.Impl.Child<TParent>>>' への暗黙的な参照変換がありません。 E:\kazuhiro\Software\test\WPF\Behavior\ConsoleApplication1\ConsoleApplication1\Impl\Child.cs 9 18 ConsoleApplication1


    2015年2月24日 13:32

回答

  • 難しいですね。
    無駄に複雑になっている感がありますが、以下のような感じでどうでしょうか。

            static void Main(string[] args)
            {
                var a1 = new AParent();
                var a2 = new AChild();
                a1.Child = a2;
                a2.Parent = a1;
                
                var b1 = new BParent();
                var b2 = new BChild();
                b1.Child = b2;
                b2.Parent = b1;
            }
    
            interface IParent { }
            interface IChild { }
            abstract class ParentBase<TChild> : IParent where TChild : IChild 
            {
                public TChild Child { get; set; }
            }
            abstract class ChildBase<TParent> : IChild where TParent : IParent
            {
                public TParent Parent { get; set; }
            }
    
            class AParent: ParentBase<AChild> { }
            class AChild : ChildBase<AParent> { }
    
            class BParent : ParentBase<BChild> { }
            class BChild : ChildBase<BParent> { }
    多分、ジェネリック以外でもっと良いアプローチがあるような気がします。
    オブジェクトのモデル例まで出ると代替案が出るかもしれませんね。

    2015年2月25日 1:09
    モデレータ
  • この問題(パラレル継承階層)はある程度妥協しないと仕方がないと思います。
    私は共通の基底クラスを設けた上でお互いを示すプロパティを上書きしています。
    NetFrameworkの DataTable→TypedDataTable, DataRow→TypedDataRow も同様です。
    public class Parent {
        public TChild Child { get; set; }
    }
    
    public class Child {
        public TParent Parent {get; set; }
    }
    
    public class Parent<TChild> : Parent
        where TChild : Child
    {
        public new TChild Child {
            get { return (TChild)base.Child; }
            set { base.Child = value; }
        }
    }
    
    public class Child<TParent> : Child
        where TParent : Parent
    {
        public new TParent Parent
            get { return (TParent)base.Parent; }
            set { base.Parent = value; }
        }
    }
    どうしてもダウンキャスト無しと言うことであれば最後のリンクが参考になるかもわかりません。
    参考
    http://systemartlaboratory.com/parallelinheritance.html 拙作
    http://csharper.blog57.fc2.com/blog-entry-130.html
    http://csharper.blog57.fc2.com/blog-entry-133.html (1階層なら書けるが複雑)

    http://systemartlaboratory.com/



    • 編集済み 三輪の牛 2015年2月25日 18:42 基底クラス指定漏れ
    • 回答の候補に設定 星 睦美 2015年2月27日 1:00
    • 回答としてマーク 星 睦美 2015年3月2日 4:57
    2015年2月25日 7:44

すべての返信

  • こんにちは。

    定義のみであればインターフェイスを使って実現してみては如何ですか。

            interface IHoge { }
            class Parent<TChild> : IHoge
                where TChild : IHoge
            {
                public TChild Child { get; set; }
            }
    
            class Child<TParent> : IHoge
                where TParent : IHoge
            {
                public TParent parent { get; set; }
            }

    ただ、これ無理じゃないですかね…。
    宣言時にはジェネリック型は確定していないといけないと思うので、

    Parent<Child<Parent<Child<Parent<...>>>>> hoge;

    このように循環するので宣言できないのでは。

    どういう目的のクラスなんでしょうか?
    代替案を考えたほうが良い気がします。


    2015年2月24日 14:16
    モデレータ
  • 普通に考えると

    class Parent<T> {
      public Child<T> Child { get; set; }
    }
    
    class Child<T> {
      public Parent<T> Parent { get; set; }
    }

    で済んでしまいそうな話なのですが、このような T では要求を満たさないのでしょうか?

    2015年2月24日 16:26
  • ありがとうござます。
    残念ながら、インターフェイスでも、型パラメータに質問に記した制約を設定すると同じエラーが発生しました。
    指定した型、およびその派生クラスのみを設定できるようにしたいため、この条件は外したくありません。

    > どういう目的のクラスなんでしょうか?
    > 代替案を考えたほうが良い気がします。

    クラス間に親子の関連を持たせたいです。
    決まったクラスと関連を持たせたいので、制約は外したくないです。

    そして、このようなクラスが複数あるので、ジェネリックで実現できないかと思いました。




    • 編集済み kyasui 2015年2月24日 23:31
    2015年2月24日 23:25
  • ありがとうございます。

    決まった相手と親子の関連を持たせたいので、Tの制約は外したくありません。
    言語仕様的に無理なんでしょうか。

    以下からC#の言語仕様を取得してジェネリックのところを読んでみたのですが、
    実現できるのかどうか、正直わからなかったです。

     https://www.microsoft.com/en-us/download/details.aspx?id=7029

    2015年2月24日 23:30
  • こんにちは
    できるかどうかを知りたいならあまり参考にならないかもしれませんが、クラス設計を考えすぎてプログラムが複雑になっていないでしょうか。

    自分もそうなのですが、ややこしいクラスの関連になったときは一歩戻るのもよい気もします。
    ややこしい設計は、あとから悲しい思いをしそうで。

    一息つけてから、オブジェクト指向、原則、デザインパターンだったりをもう一度考えてみてもよいかもしれません。
    必要なパラメータはどこで約束しようかな、みたいな話かも。
    2015年2月25日 0:32
  • 難しいですね。
    無駄に複雑になっている感がありますが、以下のような感じでどうでしょうか。

            static void Main(string[] args)
            {
                var a1 = new AParent();
                var a2 = new AChild();
                a1.Child = a2;
                a2.Parent = a1;
                
                var b1 = new BParent();
                var b2 = new BChild();
                b1.Child = b2;
                b2.Parent = b1;
            }
    
            interface IParent { }
            interface IChild { }
            abstract class ParentBase<TChild> : IParent where TChild : IChild 
            {
                public TChild Child { get; set; }
            }
            abstract class ChildBase<TParent> : IChild where TParent : IParent
            {
                public TParent Parent { get; set; }
            }
    
            class AParent: ParentBase<AChild> { }
            class AChild : ChildBase<AParent> { }
    
            class BParent : ParentBase<BChild> { }
            class BChild : ChildBase<BParent> { }
    多分、ジェネリック以外でもっと良いアプローチがあるような気がします。
    オブジェクトのモデル例まで出ると代替案が出るかもしれませんね。

    2015年2月25日 1:09
    モデレータ
  • 「決まった相手」をもっと具体的に表現することで最終的に C# でも表現できるようになります。

    ちなみに私の提示した例でも Parent<int> は必ず Child<int> を返すようになっています。もちろん同じ型でなく Parent<int> と Child<string> で親子関係という組み合わせを表現する記述の仕方も可能ではありますが、無関係な2つの型で親子関係を組む意義が感じられないのでコード例は挙げません。

    2015年2月25日 1:12
  • この問題(パラレル継承階層)はある程度妥協しないと仕方がないと思います。
    私は共通の基底クラスを設けた上でお互いを示すプロパティを上書きしています。
    NetFrameworkの DataTable→TypedDataTable, DataRow→TypedDataRow も同様です。
    public class Parent {
        public TChild Child { get; set; }
    }
    
    public class Child {
        public TParent Parent {get; set; }
    }
    
    public class Parent<TChild> : Parent
        where TChild : Child
    {
        public new TChild Child {
            get { return (TChild)base.Child; }
            set { base.Child = value; }
        }
    }
    
    public class Child<TParent> : Child
        where TParent : Parent
    {
        public new TParent Parent
            get { return (TParent)base.Parent; }
            set { base.Parent = value; }
        }
    }
    どうしてもダウンキャスト無しと言うことであれば最後のリンクが参考になるかもわかりません。
    参考
    http://systemartlaboratory.com/parallelinheritance.html 拙作
    http://csharper.blog57.fc2.com/blog-entry-130.html
    http://csharper.blog57.fc2.com/blog-entry-133.html (1階層なら書けるが複雑)

    http://systemartlaboratory.com/



    • 編集済み 三輪の牛 2015年2月25日 18:42 基底クラス指定漏れ
    • 回答の候補に設定 星 睦美 2015年2月27日 1:00
    • 回答としてマーク 星 睦美 2015年3月2日 4:57
    2015年2月25日 7:44
  • こんばんは。
    返事が遅れ、申し訳ありません。

    アドバイスありがとうございます。
    設計が複雑になりすぎていないか見直して見ます。
    ジェネリックを万能に思いすぎてたのかもしれません。

    2015年2月25日 12:24
  • こんばんは。
    返事が遅くなり、申し訳ありません。

    具体的な案をありがとうございます。
    確かに狙っていることができているように思います。
    実際のプログラムに組み込んで問題ないかを確認してみます。


    • 編集済み kyasui 2015年2月25日 13:01
    2015年2月25日 12:59
  • こんばんは。
    返事が遅くなり、申し訳ありません。

    親子関係というより、所有関係の方が正しかったです。
    教えていただいたコードは親子関係を組みたいときに参考にさせていただきます。

    • 編集済み kyasui 2015年2月25日 14:17
    2015年2月25日 14:16
  • 質問及び返信を読んでも何を求められているのかわかりません。

    「決まった相手」でも「親子関係」でも「所有関係」でも表現は何だっていいんですが、同じことを指しているようにしか読めませんし、それが何を求めているのか、もしくは逆に私の提示した例で何が不足しているのか、読み取れません。

    質問内容において、ジェネリック型はいくつ登場するのでしょうか?

    1. Parent<T>とChild<T>の2つ
    2. Parent<T1>とChild<T1>、Parent<T2>とChild<T2>の型としてはやっぱり2つ
    3. Parent1<T1>とChild1<T1>、Parent2<T2>とChild2<T2>の4つ

    1や2であれば(決まった相手|親子関係|所有関係)が1つに定められ、型パラメーター等で制約を付ける必要はそもそもないはずですが?

    2015年2月25日 15:28
  • こんばんは。
    返事が遅くなり、申し訳ありません。

    パラレル継承という、一般的な問題でしたか。
    勉強不足でした。

    ダウンキャストなしが理想ですが、型パラメータを増やすのも使いにくくて嫌ですね。
    パラレル継承の観点で、今の設計に問題がないかを検討してみます。
    ありがとうございます。

    2015年2月25日 17:19