質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

インターフェース

インターフェイスという用語はハードウェア・ソフトウェアの両方に使うことができます。 一般的に、インターフェイスは内部処理の詳細を見せないように設定されます。オブジェクト指向プログラミングにおいて、インターフェイスはabstractクラスとして定義されます。

Q&A

解決済

4回答

530閲覧

[C# .NETFramework4.8]interfaceで自身を実装した型を返すことを強制し、別のクラスでは実装した型を保持。さらに別のクラスで実装した型特有のプロパティへアクセスしたい

marusa

総合スコア17

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

インターフェース

インターフェイスという用語はハードウェア・ソフトウェアの両方に使うことができます。 一般的に、インターフェイスは内部処理の詳細を見せないように設定されます。オブジェクト指向プログラミングにおいて、インターフェイスはabstractクラスとして定義されます。

0グッド

0クリップ

投稿2024/10/15 14:54

実現したいこと

言葉でうまく説明できないのですが、下記のような構造を実現したいです。

IHoge.cs

1public interface IHoge 2{ 3 IHoge CreateInstance(); 4}

Hoge.cs

1public class Hoge : IHoge 2{ 3 public string Prop1 { get; } 4 public IHoge CreateInstance(){ 5 return new Hoge(); 6 } 7}

Hoge2.cs

1public class Hoge2 : IHoge 2{ 3 public IHoge CreateInstance(){ 4 return new Hoge2(); 5 } 6}

HogeUseBase.cs

1abstract public class HogeUseBase<T> where T : IHoge, new() 2{ 3 public List<T> hogelist = new List<T>(); 4 5 public void CreateHoge(){ 6 var hoge = new T(); 7 hogelist.Add(hoge.CreateInstance()); 8 } 9 10 abstract public void UseHoge(); 11}

HogeUse.cs

1public class HogeUse : HogeUseBase<Hoge> 2{ 3 override public void UseHoge(){ 4 foreach(var h in this.hogeList){ 5 string prop = h.Prop1; 6 } 7 } 8}

場合によってHoge, Hoge2どちらを使うか変えたいため、IHogeインターフェースに
CreateInstanceメソッドをシグネチャとして定義しました。
(補足の質問: .NETFramework4.8ではstaticなシグネチャやインスタンスのシグネチャをinterfaceで定義できないので仕方なくこの形にしました。この部分も良い実装があればお聞きしたいです。)
また、HogeUseBaseではHogeかHoge2かは意識せず扱える部分のみ実装してあります。

HogeUseでは、Hogeのプロパティへのアクセスを行いたいです。

発生している問題・分からないこと

実現したいことの書き方の場合、HogeUseBaseの7行目でIHogeからTへ変換できないエラーとなる

該当のソースコード

特になし

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

HogeUseBaseでジェネリック型を使わない以下の書き方の場合は
HogeUse5行目でHogeのプロパティへアクセスできない。

HogeUseBase.cs

1abstract public class HogeUseBase<T> where T : IHoge, new() 2{ 3 public List<IHoge> hogelist = new List<IHoge>(); 4 5 public void CreateHoge(){ 6 var hoge = new T(); 7 hogelist.Add(hoge.CreateInstance()); 8 } 9 10 abstract public void UseHoge(); 11}

補足

C# .NETFramework4.8

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

こんにちは。

かなり関数指向の考え方ですが、インターフェース定義を以下のようにすることで、そのようなコードを再現することは可能です。
説明が難しいのでひとまずコードで表現します。

csharp

1public interface IHoge<THoge> // ジェネリック型引数を持ち、自身の型を再帰的に定める 2 where THoge : IHoge<THoge> 3{ 4 THoge CreateInstance(); 5} 6 7public class Hoge : IHoge<Hoge> // Hoge の THoge は Hoge 8{ 9 public string Prop1 { get; } 10 11 public Hoge CreateInstance() 12 { 13 return new Hoge(); 14 } 15} 16 17public class Hoge2 : IHoge<Hoge2> // Hoge2 の THoge は Hoge2 18{ 19 public Hoge2 CreateInstance() 20 { 21 return new Hoge2(); 22 } 23} 24 25public abstract class HogeUseBase<THoge> 26 where THoge : IHoge<THoge>, new() 27{ 28 public List<THoge> hogelist = new List<THoge>(); 29 30 public void CreateHoge() 31 { 32 var hoge = new THoge(); 33 hogelist.Add(hoge.CreateInstance()); // new T ができれば CreateInstance はいらないのではないか? 34 35 // hogelist.Add(new THoge()); // これでいい 36 } 37 38 abstract public void UseHoge(); 39} 40 41public class HogeUse : HogeUseBase<Hoge> 42{ 43 public override void UseHoge() 44 { 45 foreach (var h in this.hogelist) 46 { 47 string prop = h.Prop1; 48 } 49 } 50} 51 52// 自身の型と異なる型を型引数にセットしたクラスも実装できることはできるが、 53// HogeUseBase が THoge = IHoge<THoge> となることを要求しているため、 54// このクラスは HogeUseBase に利用することができないため問題にならない 55public class HogeError : IHoge<HogeUnknown> 56{ 57 public HogeUnknown CreateInstance() 58 { 59 return new HogeUnknown(); 60 } 61}

質問や疑問点がありましたらコメントにお願いします。

投稿2024/10/16 01:20

tamoto

総合スコア4228

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

この質問の本旨は安全なダウンキャストの実装だと思います
ジェネリック型であるTIHogeで制限すると、TIHogeであることは保障されますが、その子となるクラスの範囲までは確約されません
よってvar hoge=new T()IHoge hoge=new IHoge()と解釈されます
インターフェイスは単体でインスタンス化が不可能なのでエラーが発生するという理屈です

これはダウンキャストが危険とされる根拠の一つです
ある特定の派生型を取得したい場合は、CreateHogeにジェネリック型を指定するのが最も簡易でしょう

using System; using System.Collections.Generic; public class Program { public static void Main() { var hoge=new HogeUse(); hoge.CreateHoge(); hoge.UseHoge(); } } public interface IHoge<T> where T:IHoge<T>,new() { T CreateInstance(); } public class Hoge : IHoge<Hoge> { public string Prop1 { get; }="Success!"; public Hoge CreateInstance()=>new Hoge(); } public class Hoge2 : IHoge<Hoge2> { public Hoge2 CreateInstance()=>new Hoge2(); } abstract public class HogeUseBase<T> where T : IHoge<T>, new() { public List<T> hogelist = new List<T>(); public void CreateHoge(){ var hoge = new T(); hogelist.Add(hoge.CreateInstance()); } abstract public void UseHoge(); } public class HogeUse : HogeUseBase<Hoge> { override public void UseHoge(){ foreach(var h in base.hogelist) Console.Write( h.Prop1); } }
Success!

因みにforeachには暗黙のダウンキャストが備わっており、基底クラスから任意のサブクラスへ動的にキャストしてくれます

そのためジェネリック型を返す基底型のメソッドを、任意の派生型を返すメソッドとしてサブクラスでオーバーライドしても、共変戻り値という機能によって問題なく実装でき、かつそれによって与えられたインスタンスの型をforeachが暗黙に検出してくれます

ジェネリック型のダウンキャストでは基本的にこの手法を採るようにすると安全です

うっかり最新版準拠のコードを貼ってしまったので書き直しました

多重継承デザイン

インスタンス化を回避したいという旨に沿い、こちらの方法も考えました
ジェネリック型を多重継承仕様で制約することにより、実質的に実装を強制するパターンです

メソッドを分離するための基底型を定義し、インターフェイスと併せて派生型に継承させます
これにより、ジェネリック型で受け付けるクラスの性質を特定の静的メソッドを実装した型に制限します

public interface IHoge{ } public abstract class FactoryIHoge{ public static T CreateInstance<T>() where T:IHoge,new() =>new T(); } public class Hoge : FactoryIHoge,IHoge { public string Prop1 { get; }="Success!"; } public class Hoge2 : FactoryIHoge,IHoge { } abstract public class HogeUseBase<T> where T:FactoryIHoge,IHoge,new() { public List<T> hogelist = new List<T>(); public void CreateHoge() =>hogelist.Add(FactoryIHoge.CreateInstance<T>()); abstract public void UseHoge(); } public class HogeUse : HogeUseBase<Hoge> { override public void UseHoge(){ foreach(var h in this.hogelist) Console.WriteLine(h.Prop1); } }

静的メソッドを利用することで、余計なインスタンス化を抑止します

IHogeのみを実装する型とそれ以外、すなはち必要な実装を伴う型とを明確に区別する為に、ここではFactoryIHogeIHogeを実装していません

捕捉

最後に迷いましたが原因の正確な切り分けの為に原文を一部訂正します

ジェネリック型であるTをIHogeで制限すると、TがIHogeであることは保障されますが、その子となるクラスの範囲までは確約されません

ここは解釈違いで、単純にT=Hogeの場合にCreateInstanceから返される戻り値がIHogeであることで、HogeのインスタンスにIHogeを代入する形となって発生する変換エラーでした
よってIHogeT(Hoge)に変換することが出来ず、またそれ故にキャストによってエラーが回避されます

しかしダウンキャストではIHogeから必ず目的の派生型に変換できるとは限らないため、危険な操作であることに違いはありません
そのため原文はそのまま残しておきます

以上を踏まえると、原形のコードは以下の形に修正することも可能です

abstract public class HogeUseBase<T> where T : IHoge, new() { public List<IHoge> hogelist = new List<IHoge>(); public void CreateHoge(){ var hoge = new T(); hogelist.Add(hoge.CreateInstance()); } abstract public void UseHoge(); } public class HogeUse : HogeUseBase<Hoge> { override public void UseHoge(){ foreach(Hoge h in this.hogelist){ Console.Write(h.Prop1); } } }

foreachの暗黙的なダウンキャストについては冒頭で説明した通りです

投稿2024/10/15 16:23

編集2024/10/16 02:41
Manabu

総合スコア60

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

marusa

2024/10/17 08:33

詳細な解説と、キーワード分かりやすくしていただきありがとうございます。 IHogeにIHogeの派生型を渡すという発想が出てきませんでした。 後述の多重継承デザインの実装について質問したいのですが、 派生型によってコンストラクタの振る舞いを変える実装にしたい場合は 適用できないという理解であっていますでしょうか。 (原文がデフォルトコンストラクタのみの記述で申し訳なかったのですが、下記のように同じシグネチャのコンストラクタの実装を強制したい) ``` public Hoge : IHoge { public Hoge(string param){ // Hoge用の初期化処理 } } public Hoge2 : IHoge { public Hoge2(string param){ //Hoge2用の初期化処理 } } ``` 前提として、static abstractなメソッドは持てず、それらを 派生型でoverrideすることも出来ないと認識しています。
Manabu

2024/10/17 15:49

不可能ではありませんが、C#ではコンストラクタのオーバーライドや抽象メソッド化には対応していません そのような制約を設ける場合、コンストラクタの引数を解析し、目的の型に合致するかを判定するロジックが必須です C#でこれを行うにはSystem.Refrectionのクラスを利用しますが、パフォーマンス上の問題からあまり推奨はしません 必要な場合はサンプルコードを用意しますが、可読性は著しく低下します 一方でコンストラクタの制約を考えないのならば、デリゲードメソッドとしてインスタンス化を実装し、評価を遅延させるという方法があります
guest

0

質問しておいてなんですが、いったんはHogeUseBaseの7行目でCreateInstanceの返り値をTでキャストして解決としました。
何か無理やりな気がするので、別の方法あればお聞きしたいです。

abstract public class HogeUseBase<T> where T : IHoge, new() { public List<T> hogelist = new List<T>(); public void CreateHoge(){ var hoge = new T(); hogelist.Add((T)hoge.CreateInstance()); } abstract public void UseHoge(); }

投稿2024/10/15 15:22

marusa

総合スコア17

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

draq

2024/10/15 17:27

その対応だと、こんなクラスを作られたときに実行時にキャスト失敗します。 public class Hoge3 : IHoge { public IHoge CreateInstance(){ return new Hoge1(); // 自分とは異なる型のインスタンスを返している } } バグの元なので、Manabuさんが書かれている方法やそもそもファクトリーパターンを採用するなどする方がよい気がします。
marusa

2024/10/17 08:33

コメントありがとうございます。 確かに、考えが及びませんでした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問