差分表示


#freeze
*&date(Y-n-j[lL],2016/11/19); C++TemplateのつもりでC#Generics使ってハマる(初歩)

人様のソース改修でロクに知らないC#をここ1,2ヶ月さわってた。~
といってもそのソースもC#慣れしたわけでないC系ユーザーが書いたような感じで ある意味助かったのだけれど、コピペ膨れなソースだったので、処理をまとめようと C++Template 的に Generics を使おうとして...ちょっとハマった。

C++だと

 #include <stdio.h>
 class Foo {
 public:
     void Run() { printf("Foo!"); }
 };
 template<class T>
 class FooMgr {
 public:
     void Run() { T().Run(); }
 };
 class Bar : public Foo {
 public:
     void Run() { printf("Bar!"); }
 };
 class BarMgr : public FooMgr<Bar> {
 };
 int main() {
     BarMgr barMgr;
     barMgr.Run();
 }

で "Bar!!" と出力されるようなつもりで

 class Foo {
     public void Run() { System.Console.Write("Foo!"); }
 }
 class FooMgr<T>
     where T : Foo, new()
 {
     public void Run() { (new T()).Run(); }
 }
 class Bar : Foo {
     public new void Run() { System.Console.Write("Bar!!"); }
 }
 class BarMgr : FooMgr<Bar> {
 }
 class Test {
     public static void Main() {
         BarMgr barMgr = new BarMgr();
         barMgr.Run();
     }
 }

のように書いてみたところ、実行したら "Foo!" が出力される、と。

初歩的な勘違いバグの類で、C# Generics って C++ Template ほど Macro的な強力さはなくて、この場合 継承class のラッパー的なモノでしかないのね。~
void FooMgr<Bar>.Run() { (new Bar()).Run(); } のような実体(関数)の自動生成を期待しちゃってたのだけれど、FooMgr<Foo>.Run() に毛の生えた実体が使い回される感じらしい。
void FooMgr<Bar>.Run() { (new Bar()).Run(); } のような実体(関数)の自動生成を期待しちゃってたのだけれど、FooMgr<Foo>.Run() に毛の生えた程度の実体が使い回される感じらしい。

where T : Foo で継承的な記述からとっとと気づけよなんだけど、ググって見よう見まね、要求を書いてるだけのつもりになってた。~
修正は素直に virtual / override を使えばよく。

 class Foo {
     public virtual void Run() { System.Console.Write("Foo!"); }
 }
 class FooMgr<T>
     where T : Foo, new()
 {
     public void Run() { (new T()).Run(); }
 }
 class Bar : Foo {
     public override void Run() { System.Console.Write("Bar!!"); }
 }
 class BarMgr : FooMgr<Bar> {
 }
 class Test {
     public static void Main() {
         BarMgr barMgr = new BarMgr();
         barMgr.Run();
     }
 }

どうせbase側からアクセスしないのだからと無精してTemplate使いたかったわけで多少本末転倒な気分もあるのだけれど、実作業では結局virtualすべき多態な事案になってしまったのでコレはコレでよしとしとく。

※も1つアホだったのは、new で継承元メソッド上書きしてたものだから、後から根本を virtual に変えても orverrideすべきものがnewのままでコンパイル通っちゃってハマるワナ