1つだけ過去を変えられるとしたら?

C# です。
型 (is) で分岐すると到達不能になってしまうことがあります。
class Program
{
  abstract class B {}
  class D1 : B { public void Guitar() { Console.WriteLine("guitar"); } }
  class D2 : B { public void Baseball() { Console.WriteLine("baseball"); } }
  class D3 : D1 { public void Cards() { Console.WriteLine("cards"); } }
  static void play(IEnumerable<B> b)
  {
    foreach (var d in b)
    {
      if (d is D1) { ((D1)d).Guitar(); }
      else if (d is D2) { ((D2)d).Baseball(); }
      else if (d is D3) { ((D3)d).Cards(); /* unreachable */ }
    }
  }
}
class B に virtual な Play() を追加して D1, D2, D3 で override すればいいのですが、B, D1, D2, D3 を変更できない場合には、どのようなテクニックがあるでしょうか。
教えてください。
http://rextester.com/VYLYMV11858

A 回答 (6件)

なんかよく分かりませんが、ただラップしたいならインターフェースとラップクラスを用意すればできますよね。


using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var a = new IWrap[] { new W1(), new W2(), new W3() };

Console.WriteLine("ふつう");
foreach (var b in a)
{
b.Play();
}

Console.WriteLine("メソッド");
ConsoleApp1.Program.play(a);

Console.Read();
}

static void play(IEnumerable<IWrap> b)
{
foreach (var d in b)
{
d.Play();
}
}

// START 変更不可クラス群
abstract class B
{
}
class D1 : B
{
public void Guitar()
{
Console.WriteLine("guitar");
}
}
class D2 : B
{
public void Baseball()
{
Console.WriteLine("baseball");
}
}
class D3 : D1
{
public void Cards()
{
Console.WriteLine("cards");
}
}
// END 変更不可クラス群

// START ラップ用クラス群
interface IWrap
{
void Play();
}
class W1 : IWrap
{
public void Play()
{
var d = new D1();
d.Guitar();
}
}
class W2 : IWrap
{
public void Play()
{
var d = new D2();
d.Baseball();
}
}
class W3 : IWrap
{
public void Play()
{
var d = new D3();
d.Cards();
}
}
// END ラップ用クラス群
}
}

何百とあるクラスのラップクラスを用意するのが面倒だから、当該クラスのラップクラスがないにも関わらずラップクラスとして取得したいという話ならラップ云々の話ではないと思います。

もはや元々の質問と異なるので、一度締めて再度正確な質問を改めて投稿した方がよいでしょう。
どういうクラスの実装は一切変更できず、どういうラッパークラスを用意して、何が問題なのかを実際のコードで具体的に。

また、VSの質問ならば、ここではなくMSDNフォーラム(https://social.msdn.microsoft.com/forums/ja-jp/h …で質問(カテゴリ:Visual Studio Development → Visual C#)で質問した方が有識者からのアドバイスは得易いかと思います。
    • good
    • 0

ついでに。



よくわかりませんが、例えば、D1, D2, D3というようなクラスが大量にあるとするならば、すでに回答があるように、基本的にはAdapterパターンのようにラッパークラスを用意して、それを処理するか(http://qiita.com/shoheiyokoyama/items/bd1c692db4 …)、特定のクラスまたはメソッド内で現行動作のように、if()やswitch()を利用して各クラスおよびメソッドを把握して実行するかのどちらかしかないでしょう。

んで、そういう話ではなくて、D1, D2, D3クラスであるかどうか、そのメソッドはなんなのか、を把握したくないという話なら、ルールが決まっているならメソッドを直接叩くという荒業もあるでしょう。
以下コードは、条件に合致する場合にうまく動作します。
スマートなコードかどうかは分かりませんが・・・。

合致するものが2件以上ある場合は1件目を正として実行します。
【条件】
・publicである。
・abstractでない。
・引数がない。
・対象クラス内で初めて定義されたメソッドである。
・対象クラスに、上記条件のメソッドは必ず1つである。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Rextester
{
class Program
{
static void Main(string[] args)
{
play(new B[] { new D1(), new D2(), new D3(), });
Console.Read();
}

abstract class B { }
class D1 : B { public void Guitar() { Console.WriteLine("guitar"); } }
class D2 : B { public void Baseball() { Console.WriteLine("baseball"); } }
class D3 : D1 { public void Cards() { Console.WriteLine("cards"); } }

static void play(IEnumerable<B> b)
{
foreach (var d in b)
{
var methods = d.GetType().GetMethods().Where(
x => x.IsPublic &&
!x.IsAbstract &&
x.GetParameters().Length == 0 &&
x.DeclaringType.Name == d.GetType().Name
).Select(x => x);
if (methods.Count() < 1)
{
throw new ArgumentException("呼び出し可能なメソッドがないクラスが引数に設定された");
}
var method = methods.First();

method.Invoke(d, null);
}
}
}
}
    • good
    • 0
この回答へのお礼

ありがとうございます。問題を整理しました。

(1) B の既知の派生クラス D1, D2, ... のすべてに (B ではない) 共通点 W { Play() } を見出して、アダプターパターン (委譲) で W を D1, D2, ... それぞれに W1 : W { D1 ee }, W2 : W { D2 ee }, ... と実装しました。
(2) D1, D2, ... のインスタンスは (コンポジットパターンで) すべて B として格納されています (ここでは簡単のため IEnumerable<B>)。
(3) D1, D2, ... には継承関係があります。
(4) IEnumerable<B> には未知の (undocumented) クラスがありえますが、既知の (documented) クラス D1, D2, ... のいずれかの派生クラスです。
(5) IEnumerable<B> のそれぞれをそれぞれに最も適した W1, W2, ... でラップした IEnumerable<W> を作りたいです。

お礼日時:2017/05/31 03:12

//Rextester.Program.Main is the entry point for your code. Don't change it.


//Compiler version 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Rextester
{
public class Program
{
public static void Main(string[] args)
{
play(new B[] { new D1(), new D2(), new D3(), });
}
abstract class B {}
class D1 : B { public void Guitar() { Console.WriteLine("guitar"); } }
class D2 : B { public void Baseball() { Console.WriteLine("baseball"); } }
class D3 : D1 { public void Cards() { Console.WriteLine("cards"); } }
static void play(IEnumerable<B> b)
{
foreach (var d in b)
{
var type = d.GetType();
if (type == typeof(D1)) { ((D1)d).Guitar(); }
else if (type == typeof(D2)) { ((D2)d).Baseball(); }
else if (type == typeof(D3)) { ((D3)d).Cards(); }
}
}
}
}
    • good
    • 0

「あるクラスか、その子孫クラス」ではなく、「ある特定のクラス」と判定する方法あったはず、と調べたところ


GetType が使えるとわかりました
http://ufcpp.net/study/csharp/oo_polymorphism.html



あとは、D1,D2,D3をラップしたwD1,wD2,wD3を作って、playメソッドをオーバーライドする、とか。
    • good
    • 0
この回答へのお礼

ありがとうございます。

実は問題はその適切なラッパーを選択する部分なのです
abstract class W { public abstract void Play(); }
abstract class W<T> : W { public W(T w) { this.EE = w; } public T EE { get; protected set; } }
class W1: W<D1> { public W1(D1 w) : base(w) {} public override void Play() { this.EE.Guitar(); } }
class W2: W<D2> { public W2(D2 w) : base(w) {} public override void Play() { this.EE.Baseball(); } }
class W3: W<D3> { public W3(D3 w) : base(w) {} public override void Play() { this.EE.Cards(); } }
IEnumerable<W> Wrap(IEnumerable<B> b) {
  foreach (var d in b) {
    if (d is D1) yield return W1((D1)d);
    else if (d is D2) yield return W2((D2)d);
    else if (d is D3) yield return W3((D3)d);
  }
IEnumerable<B> に class D3ex : D3 {} のインスタンスが含まれている場合、それは最適な W3 で包みたいです。

お礼日時:2017/05/23 23:45

しつこくてごめんなさい。



評価の順番を変えればいいんですもんね。

あと、「型スイッチ」というのがあり、上から順番に評価されるので、
switch( d ) {
case D3 :
;
case D2 :
;
case D1 :
;
}
とやるのも一興かと。
    • good
    • 0
この回答へのお礼

継承関係は調べてませんが、B, D1, D2, ... は百個近くあります。
今、考えているのは、
1. Type.IsSubcalssOf() を半順序として、T4 で if else を並べ替える (B, D1, D2, ... は外部の DLL にあります)。
2. オーバーロード Play(D1 d1), Play(D2 d2), Play(D3 d3) を用意して、実行時コード生成でコンパイラにオーバーロードを選択させる。
3. たぶん 2 と同じことですが、(dynamic) にキャストして実行時にオーバーロードを選択させる。
です。

お礼日時:2017/05/23 23:47

なるほど、やってみると D3 が D1 でもあり、Bでもあるということですな。


d is D1 → true,
d is D3 → true, と。
であれば
最初の if( d is D1 ) を
if( ( d is D1 ) && !( d is D3 ) ) // D1 だけど D3 じゃないとき
にするとか。

私は馬鹿なので、もうちょっとスマートな書き方があるはずです。

勉強になりました。ありがとうございます。
    • good
    • 0
この回答へのお礼

ありがとうございます。
そうですね。GoF のようなスマートな方法を探しています。

お礼日時:2017/05/23 23:50

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!