teratail header banner
teratail header banner
質問するログイン新規登録
C#

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

Q&A

解決済

1回答

529閲覧

実際の型のメソッドが呼ばれない

fana

総合スコア12229

C#

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

0グッド

0クリップ

投稿2025/06/27 05:28

編集2025/06/27 05:35

0

0

下記コード内に注釈で記した振る舞いに困っています.

  • なぜこうなるのでしょうか?
  • どう修正すれば良いのでしょうか?(=どうすれば期待している動作を達成できるのでしょうか?)

(追記: .NET Framework 4.8 を使用しています)

C#

1// 2//問題が再現するコード 3// 4 5public interface ICopyable<T> 6 where T : class 7{ void Copy( T Src ); } 8 9public abstract class Base : ICopyable<Base> 10{ 11 public int BaseData{ get; set; } 12 13 public void Copy( Base Src ){ BaseData = Src.BaseData; } 14 15 public int VeryImportantWork() 16 { 17 //... 18 //... 19 return VeryImportantDecision(); 20 } 21 22 protected abstract int VeryImportantDecision(); 23} 24 25public class Deriv : Base, ICopyable<Deriv> 26{ 27 public int DerivData{ get; set; } 28 29 public Deriv() : this( 0,0 ) { } 30 public Deriv( int b, int d ){ BaseData = b; DerivData = d; } 31 32 public void Copy( Deriv Src ) 33 { 34 DerivData = Src.DerivData; 35 base.Copy( Src ); 36 } 37 38 protected override int VeryImportantDecision() 39 { return BaseData + DerivData; } 40} 41 42public class Test<T> 43 where T : Base, ICopyable<T>, new() 44{ 45 public int DoSomething( T Src ) 46 { 47 var XXX = new T(); 48 49 //※1 : 50 //T の型が Deriv であるとき, Deriv.Copy(Deriv) が呼ばれて欲しいわけだが 51 //コレが Base.Copy(Base) になってしまう. 52 //( where句の ICopyable<Deriv> よりも Base が優先されている??) 53 XXX.Copy( Src ); 54 55 //... 56 return XXX.VeryImportantWork(); 57 } 58} 59 60class Program 61{ 62 static void Main(string[] args) 63 { 64 var t = new Test<Deriv>(); 65 66 //※2 : 67 //結果として 120 が返されることを期待しているが,(上記※1によって) 20 が返される 68 int Result = t.DoSomething( new Deriv(20,100) ); 69 Console.WriteLine( Result.ToString() ); 70 Console.Read(); 71 } 72}

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

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

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

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

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

guest

回答1

0

ベストアンサー

こんにちは。

Test<T> クラスの T 制約が Base, ICopyable<T> となっていることで、XXX.Copy(Src); のメソッド呼び出しは void Base.Copy(Base) の呼び出しに解決されます。

要は ICopyable<T>.Copy(T) メソッドを選択できればよいので、

csharp

1((ICopyable<T>)XXX).Copy(Src);

とすれば意図した動作にできるかと思います。

投稿2025/06/27 06:19

tamoto

総合スコア4346

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

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

fana

2025/06/27 08:14

> 制約が Base, ICopyable<T> となっていることで、XXX.Copy(Src); のメソッド呼び出しは void Base.Copy(Base) の呼び出しに解決されます。 この解決に関するルール(?)は どこ/何 を見ると把握できますか? (where句で左側にあるほど優先とかそういう話がある?)
tamoto

2025/06/27 08:48

そう言われると、公式資料がどこにあるかちゃんと把握していませんでした…… どこかにあるはずなのですが、今のところ見つけられていません。 ルールについてもう少し詳しく書くと、 型引数制約の文法では、実体型 (class) は必ずインターフェースより先に書かれなければならないルールがありますが、この環境下では型引数の型は実質的に指定した実体型であるものとして扱われます。 その状況でインターフェースのメソッドを呼んでいる場合、あくまで実体型にあるメソッドを呼んでいる扱いになるため、インターフェースのメソッドを呼びたい場合は明示的にインターフェース型にキャストした状態でメソッドを呼ぶ必要があるということになります。 この状況はジェネリックを介さなくても、クラスに「インターフェースの明示的実装」を行った際に表れます。 また、複数のインターフェース間では解決の優先順位は平坦なので、例えば同名同引数のメソッドを持つインターフェースを2つ用意し、両方を制約に含むと、メソッド呼び出しは曖昧となりビルドが通らなくなります。 すなわち、以下のようなコードはビルドできません。 ``` public void TestMethod<T>(T value) where T : ICopyableA<T>, ICopyableB<T> // has method `void Copy(T src);` { value.Copy(default!); // <- CS0121 The call is ambiguous between the following methods or properties } ```
fana

2025/06/27 09:09

なるほど…… 制約の種類間で優先順位があって,優先側だけで解決できる部分についてはそこで(優先じゃない残りの制約の存在の影響を受けずに)解決される ……という感じなんですかね.
tamoto

2025/06/27 09:23

そうですね。 制約に T : Base とあったらとにかく T は Base なので、そこで「Base に存在するメソッド」を呼んだらそれは Base のメソッドを呼んでいる扱いになる、という感じです。 クラスのメソッドと同名同引数のインターフェースメソッドを明示的実装で実装したら、インターフェース側のメソッドはキャストしないと呼べなくなるのと同じ具合ですね。
fana

2025/06/27 10:24

激しく感謝! 実際のコードをデバッグ実行で追ってて > XXX.Copy( Src ); 相当のところで「XXX も Src も確かに Deriv 型なのに,Baseのメソッドが呼ばれてる模様. 何これ…」ってなってました. C#難しすぎ.
tamoto

2025/06/27 10:57

仮想メソッドでないのに継承関係の同名メソッドを別の実装にするというのがかなりレアケースなので、 この場合は、Base Deriv 共に、Copy メソッドを「インターフェースの明示的実装」で実装するのが適切なケースに見えますね。
fana

2025/06/27 11:14

なるほど. そうすれば Base では Copy が解決されないから ICopyable<T> 側で解決されることになるわけですね. C# 初心者なので,明示的実装って自分で書いたことない(目にしたこともほぼ無さそうな)気がしますが, 普通に他の場所で Deriv とかを単体で(その型で)使ってる場所で Copy() を呼ぶのが面倒になる…のかな.
tamoto

2025/06/27 11:40

継承関係においては、「ただアップキャストしただけなのにメソッドの挙動が変わってしまう」というのは結構な罠になるので、 Copy のような、クラス自体と紐付くメソッドなら、それぞれにインターフェースで明示的に実装されていた方が良い (呼び出しが面倒になるけど、罠を踏みにくい) とされている感じですね。 元の質問の例でも、引数 Src に渡すオブジェクトがアップキャストされているだけで Copy の挙動が変化してしまうわけなので、それが許せる内容かどうかで考える感じになりますね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問