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

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

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

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

パラメータ

関数やプログラム実行時に与える設定値をパラメータと呼びます。

継承

継承(インヘリタンス)はオブジェクト指向プログラミングに存在するシステムです。継承はオブジェクトが各自定義する必要をなくし、継承元のオブジェクトで定義されている内容を引き継ぎます。

最適化

最適化とはメソッドやデザインの最適な処理方法を選択することです。パフォーマンスの向上を目指す為に行われます。プログラミングにおける最適化は、アルゴリズムのスピードアップや、要求されるリソースを減らすことなどを指します。

Q&A

解決済

2回答

2604閲覧

ジェネリッククラスの型パラメーターに型パラメータ付きのものを指定する際、簡略化する方法

hirotamasami

総合スコア5

C#

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

パラメータ

関数やプログラム実行時に与える設定値をパラメータと呼びます。

継承

継承(インヘリタンス)はオブジェクト指向プログラミングに存在するシステムです。継承はオブジェクトが各自定義する必要をなくし、継承元のオブジェクトで定義されている内容を引き継ぎます。

最適化

最適化とはメソッドやデザインの最適な処理方法を選択することです。パフォーマンスの向上を目指す為に行われます。プログラミングにおける最適化は、アルゴリズムのスピードアップや、要求されるリソースを減らすことなどを指します。

0グッド

1クリップ

投稿2020/10/07 06:06

編集2020/10/07 06:12

前提・実現したいこと

C#のジェネリックについてですが、
以下のようなクラスがあるとすると、

DataModel.cs

C#

1class DataModel<Support> where Support : BaseDataSupport 2{ 3}

BaseDataSupport.cs

C#

1///DataModelクラスの動作するために必要な情報やその情報を加工するクラス 2class BaseDataSupport 3{ 4}

DataManager.cs

C#

1///DataModelのデータを操作したり、データを用いて何かをするクラス 2abstract class DataManager<D> where D : DataModel<BaseDataSupport> 3{ 4 D _data; 5}

このようなクラスの関係を用いて
プログラムを作成するにあたり、
上記のように
DataManagerクラスは
where制約でBaseDataSupportを型パラメーターに持つジェネリッククラス
DataModelクラスで制約した型パラメーターDを持つのですが、
DataModelですでにBaseDataSupportで型制約しているにも関わらず、
DataManagerでDataModelの制約
where : BaseDataSupport
と再び記述しなければなりません。

DataModelで制約している時点で、
その型パラメータはBaseDataSupport(またはBaseDataSupportを継承しているクラス)
に決まっているので
必要がない場合それらをDataManagerで記述したくないのですが、
それらの記述を簡略化する方法
または記述しなくていい方法はありませんでしょうか?

このようなサイトでの質問もまだ慣れておらず、情報不足ありましたら
ご指摘ください。
宜しくお願い致します。

実現したいこと

※以下全てそのまま試しましたが、使用できなかった理想例

・型名だけで指定できる

C#

1abstract class DataManager<D> where D : DataModel 2{ 3 D _data; 4}

エラーメッセージ:
エラー CS0305 ジェネリック 種類 'DataModel<Support>' を使用するには、1 型引数が必要です。

・型パラメーターを省略できる
省略した場合、制約で指定した型(上記だとBaseDataSupport)
を使用してくれる

・型パラメーターを短く1か所に定義して使える
using where S : KataParameterTest.BaseDataSupport;
のような感じで変数やusingみたいに定義してそれを使用できるようにできるなど

補足情報(FW/ツールのバージョンなど)

・OS バージョン Windows10

・継承を用いたクラス作成で別の疑問が発生したが、
当疑問などの理由によりそちらも未解決
https://teratail.com/questions/292687

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

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

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

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

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

guest

回答2

0

ベストアンサー

こんにちは。

そのようなジェネリック制約を用いた関係性の定義をするときは、通常 interface を使います。
interface の Generics は Varianceをサポートしているので、正しく使えば質問にあるようなコードも動作させることができます。

以下は一例ですが、ビルドは通ります。

csharp

1interface IDataSupport 2{ } 3 4interface IDataModel<out TSupport> // TSupport は covariant 5 where TSupport : IDataSupport 6{ } 7 8abstract class DataManager<TModel> 9 where TModel : IDataModel<IDataSupport> 10{ 11 TModel _data; 12} 13 14class SuperSupport : IDataSupport 15{ } 16 17class SuperDataSuperSupportModel : IDataModel<SuperSupport> 18{ } 19 20class SuperDataManager : DataManager<SuperDataSuperSupportModel> 21{ }

投稿2020/10/07 08:48

tamoto

総合スコア4252

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

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

hirotamasami

2020/10/08 04:40

回答ありがとうございます。 共変性というものがあるのですね。 勉強になりました。 この共変性なのですが、 ベースをinterfaceにすることで、 クラス同士の関係を大元で定義することで そこから派生したクラスを Support → DataModel → Manager の順番で実装していくところまでは理解したのですが、 DataManagerの要素をジェネリックで指定しているのは(TModelにあたる部分) このTModelに共通の要素(変数やメソッド(virtualなどで処理込み)) を保持するために定義しているのですが、 回答くださった例では、 共変性を持たせるためにTModelにあたる部分をinterfaceにされておりますので、 継承させることでできていた共通の定義を 例でいうところのIDataModelに定義することができなくなってしまいます。 ▲1 ここをベースにしたい ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ interface IDataSupport{ } interface IDataModel<out TSupport> where TSupport : IDataSupport { } // TSupport は covariant abstract class DataManager<TModel>where TModel : IDataModel<IDataSupport> { TModel _data; } ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ ▲2 ベースからの派生クラス ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ class BaseSupport : IDataSupport{ } class SuperDataSuperSupportModel : IDataModel<BaseSupport> { } class SuperDataManager : DataManager<SuperDataSuperSupportModel>{ } ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 継承の説明でよく用いられる例で車がございますので、 車,タイヤそしてそれらの車やタイヤの関係の管理を用いますと、 ▲1にあたる部分を車(BaseCar),タイヤ(BaseTire),車やタイヤの関係の管理(BaseCarManager) としこの車の定義にタイヤの数やタイヤを回転させる といったものを定義し ※いい感じの車の共通要素が思いつかなかったので上記のように定義したが、  タイヤの数やタイヤを回転させるのはBaseCarManager役割かもしれないのであくまで一例 ここから 除雪車や惑星探査車などを作成していく感じです 以下理想例 (イメージなのでコンパイルは通りません) ▲1のように作成したいベース ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ class BaseTire{ } class BaseCar<Tire> where Tire : Base { Tire _tire; public virtual int _tireCount{ get; } = 4; //継承後何もいじらなければ4輪 public virtual void TireRotation(Direction direction) { //_tireを用いて、タイヤを回転させたときに働くすべての車で共通の処理など } } abstract class DataManager<TModel>where TModel : IDataModel<IDataSupport> { TModel _data; } ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 除雪車 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ class SnowTire : BaseTire { } class Snowplow : BaseCar<SnowTire> { } class SnowplowManager : SnowplowManager<Snowplow>{ } ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 惑星探査車 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ class AirlessTire : BaseTire { } class PlanetaryRover : BaseCar<AirlessTire> { public override int _tireCount{ get => base._tireCount; } = 6; } class PlanetaryRoverManager : PlanetaryRoverManager<PlanetaryRover>{ } ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 上記のようなものを作成しようとすると もし回答してくださった一例ですと、 タイヤ数やタイヤの回転時の処理(車のクラスに必要かどうかは別として)などの共通要素 を除雪車や惑星探査車にそれぞれ定義しなければなりません 長くなってしまいましたが、 共変性を用いた定義と 継承させることで持たせられる共通の定義は 共存できないのでしょうか?
tamoto

2020/10/08 06:21 編集

言いたいことがよくわかりませんが、依存関係グラフと実装を別にすれば良いのでは? interface は単なる API の定義なので、実装は別にベースを作れば良いです。 ....ITire (interface) ........<- BaseTire (abstract class with implementation) ............<- Tire (concrete class) ............<- SnowTire (concrete class) ............<- AirlessTire (concrete class) この設計で実装するのが本当に用途にマッチしてるのかどうかは知りませんが。
hirotamasami

2020/10/08 07:08 編集

共変性も継承されるということですね 実装の仕方が悪かったのか無理だと勘違いしてました。 つまり以下のように3層構造にすればいいということですね クラスの関係紐づけ用のインターフェース ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ interface ITire { } interface ICar<out I_Tire> where I_Tire : ITire { } // I_Tire は covariant interface ICarManager<out I_Car> where I_Car : ICar<ITire> { } ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ ベースのクラス ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ class BaseTire : ITire { } class BaseCar<Base_Tire> : ICar<Base_Tire> where Base_Tire : BaseTire { Base_Tire _tire; public virtual int _tireCount { get; } = 4; //継承後何もいじらなければ4輪 public virtual void TireRotation() { //_tireを用いて、タイヤを回転させたときに働くすべての車で共通の処理など } } //class BaseDataManager<Base_Car> : ICarManager<Base_Car> where Base_Car : BaseCar<BaseTire>{ } ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 使用するクラス ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ class SnowTire : BaseTire { } class Snowplow : BaseCar<SnowTire> { } //class SnowplowManager : BaseDataManager<Snowplow> { } //これだけ無理でした class SnowplowManager : ICarManager<Snowplow> { } ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 教えていただいた方法で作成していきます。 ありがとうございました。 また何か機会がございましたら、 よろしくお願いします。
guest

0

・型パラメーターを短く1か所に定義して使える
using where S : KataParameterTest.BaseDataSupport;
のような感じで変数やusingみたいに定義してそれを使用できるようにできるなど

こんなんでどうでしょう。

csharp

1using BDSModel = DataModel<BaseDataSupport>; 2 3///DataModelのデータを操作したり、データを用いて何かをするクラス 4abstract class DataManager<D> where D : BDSModel 5{ 6 D _data; 7}

投稿2020/10/07 06:54

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

hirotamasami

2020/10/07 07:23

回答ありがとうございます。 この方法も一応前に試したことはあるんですが DataManagerで使用するD _dataは DataModel<BaseDataSupport> または、DataModel<BaseDataSupport>を継承したクラスを使用したいのですが、 この方法を用いた場合、 BDSModelがDataModel<BaseDataSupport>で決まってしまい。 DataModel<BaseDataSupport>を継承したクラスは 使用できませんでした 具体的には class SuperSupport : BaseDataSupport { } class SuperDataSuperSupportModel : DataModel<SuperSupport> { } class SuperDataManager : DataManager<SuperDataSuperSupportModel> { } のようにしたとき SuperDataManagerでエラーが出る感じです。 情報不足で申し訳ございません
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問