2011-04-08 53 views
1

我發現了一個奇怪的行爲爲泛型和重載方法。看來, 泛型的重載機制不起作用:.net通用錯誤?

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

namespace TestGeneric1 
{ 
    class Program 
    { 
     class B 
     { 
      public void f() 
      { 
       Console.WriteLine("B.f() called!"); 
      } 
     } 

     class D : B 
     { 
      public void g() 
      { 
       Console.WriteLine("D.g() called!"); 
      } 
     } 

     class H 
     { 
      public static void over(B b) 
      { 
       b.f(); 
      } 

      public static void over(D d) 
      { 
       d.g(); 
      } 
     } 

     class Gen<T> where T : B 
     { 
      T _item; 

      public Gen(T item) 
      { 
       _item = item; 
      } 


      public void test() 
      { 
       H.over(_item); 
      } 
     } 

     class Gen2 
     { 
      public static void test<T>(T item) where T : B 
      { 
       H.over(item); 
      } 
     } 


     static void Main(string[] args) 
     { 
      B b = new B(); 
      D d = new D(); 

      Console.WriteLine("Direct Call"); 
      H.over(b); // OK! 
      H.over(d); // OK; 

      Console.WriteLine("Call via Generics"); 
      Gen<B> testGenB = new Gen<B>(b); 
      Gen<D> testGenD = new Gen<D>(d); 
      testGenB.test(); // OK 
      testGenD.test(); // Wrong !!! 

      Console.WriteLine("Call via Generics 2 chance..."); 
      Gen2.test<B>(b); // OK ! 
      Gen2.test<D>(d); // wrong 
      Console.ReadKey(); 
     } 
    } 
} 

有沒有能夠解釋這個任何機構? 是否有解決方法。

我嘗試做的是實現一個通用的訪問者類(訪問者模式)與通用。 TIA

補充:

這個問題我真的盡力解決是雙重分派的問題。 假設你想要寫的,你必須 精靈之間處理衝突的一個小遊戲引擎:

interface ISprite 
{ 
    void Hit(ISprite sprite); 
} 

abstract class Sprite : ISprite 
{ 
    public void Hit(ISprite sprite) 
    { 
     Hit(sprite as Sprite); 
    } 

} 

class SpriteA : Sprite 
{ 
} 

class SpriteB : Sprite 
{ 
} 

我要做的就是實現這樣

interface IVisitorSprite 
{ 
    void Visit(SpriteA item); 
    void Visit(SpriteB item); 
} 


abstract class Sprite : ISprite 
{ 

    protected abstract void Accept(IVisitorSprite visitor); 

} 

class SpriteA : Sprite 
{ 
    protected override void Accept(IVisitorSprite visitor) 
    { 
     visitor.Visit(this); 
    } 
} 

class SpriteB : Sprite 
{ 
    protected override void Accept(IVisitorSprite visitor) 
    { 
     visitor.Visit(this); 
    } 

} 

訪問者模式的命中方法調用訪問模式兩次:

abstract class Sprite : ISprite 
{ 
    public void Hit(ISprite sprite) 
    { 
     Hit(sprite as Sprite); 
    } 

public void Hit(Sprite sprite) 
{ 
    HitResoverBuilder hitBuilder = new HitResoverBuilder(); 
    Accept(hitBuilder); 
    sprite.Accept(hitBuilder.HitResolver); 
} 

protected abstract void Accept(IVisitorSprite visitor); 


} 
class HitResoverBuilder : IVisitorSprite 
{ 
    public IVisitorSprite HitResolver { get; private set; } 

    void IVisitorSprite.Visit(SpriteA item) 
{ 
     HitResolver = new HitResolver<Penguin>(item); 
} 

void IVisitorSprite.Visit(SpriteB item) 
    { 
    HitResolver = new HitResolver<Flame>(item); 
} 
} 

class HitResolver<T> : IVisitorSprite 
{ 
    public HitResolver(T spriteOne) 
    { 
    _spriteOne = spriteOne; 
    } 

    T _spriteOne; 

void IVisitorSprite.Visit(SpriteA item) 
    { 
     HitHelper.Hit(_spriteOne, item); 
    } 

void IVisitorSprite.Visit(SpriteB item) 
    { 
     HitHelper.Hit(_spriteOne, item); 
    } 
} 

class HitHelper 
{ 
    public static void Hit(SpriteA a, SpriteB b) 
    { 
     // manage hit between spriteA and SpriteB 
    } 

    public static void Hit(SpriteB b, SpriteA a) 
    { 
     Hit(a,b); 
    } 

    public static void Hit(SpriteB b, SpriteB b1) 
    { 
     // manage hit between 2 SpriteB 
    } 

    public static void Hit(SpriteA a, SpriteA a1) 
    { 
     // manage hit between 2 SpriteA 
    } 
} 
+2

你是什麼意思的「不工作」和「錯誤的」?你期望什麼行爲,你看到了什麼行爲? – 2011-04-08 13:03:04

+0

你應該包括輸出。我可以猜測問題是什麼,但不能確定。 – 2011-04-08 13:03:56

+0

將D.g類更改爲「override f」,並使B.f virtual成爲可能,它將按預期工作。哦和H.over(D)電話需要更改爲d.f()。 – asawyer 2011-04-08 13:11:52

回答

4

我猜你看到的一切使用泛型被視爲如果它是一個實例B(這是我所期望的)。

您約束泛型類型TB。這意味着,無論您使用哪種類型的泛型參數,在確定編譯時要調用哪個重載時,它都將被視爲B的實例。這意味着,當H.over()被調用時,它使用需要的B一個實例過載。

如果你想使用泛型來實現訪問者模式,那麼你需要正確設置你的類結構(正確的重載方法是關鍵):

class B 
{ 
    public virtual void f() { Console.WriteLine("B.f() called!"); } 
} 

class D : B 
{ 
    public override void f() { g(); } 

    public void g() { Console.WriteLine("D.g() called!"); } 
} 

static class H 
{ 
    public static void over(B b){ b.f(); } 
} 
+0

這是真的,因爲它是在**編譯時**決定的,如果調用「H.over(D d)」或「H.over(B b)」。編譯器只知道'T'是'B'的子類型並且選擇了第二個重載,因爲它不知道在你的情況下,在** runtime **時,採用'D'的重載會更合適。絕對正確的行爲。 – 2011-04-08 13:11:10

+0

你的回答談論使用泛型,但你的代碼示例不使用它們。另外,我沒有看到你的解決方案實際上H(我假設它是訪問者)允許針對不同節點類型改變行爲。它只能訪問基類(B)的公共接口,不能以不同的方式處理節點,我認爲這是模式的主要原因之一。 – forsvarir 2011-04-08 13:53:56

+0

在你的代碼示例中,你得到B從D派生出來。它應該是D派生自B(它不會讓我爲你編輯它,因爲它少於6個字符的變化) – forsvarir 2011-04-08 13:55:31

1

編輯:各種變化後改寫答案問題

正如前面已經說過(見賈斯汀),你不能只使用泛型,因爲編譯器需要知道在編譯時有什麼支持的類型對象都是。目前還不清楚你試圖通過使用泛型實現什麼。如果它是保存打字,那麼這可能不是你的問題的解決方案......

這麼說,我覺得你的問題是你有未知類型的兩個小精靈(可能是從一個集合或東西):

Sprite a; // may be SpriteA, may be SpriteB 
Sprite b; // may be SpriteA, may be SpriteB 

你要協調這兩個精靈之間的碰撞,但爲了做到這一點,你需要知道的類型都精靈的,這樣就可以調用相應的功能在你的HitHelper:

public static void Hit(SpriteA a, SpriteB b) 

爲此,您需要確定sprite1的類型和sprite2,你試圖用訪問者模式來做這件事,但它不起作用。這是因爲您一次只能解析一個參數(您可以標識一個或b,但不能同時標識兩個參數)。因此,您必須重複調度兩次(每個變量一次),以便可以解決這兩個參數。

一個如何做到這一點的例子如下所示(可能有更好的方法,建議歡迎)。

// Define some SpriteTypes, that support an Invoke/visit method 

abstract class Sprite { 
    public abstract TResult Invoke<TResult>(ISpriteInvoker<TResult> invoker); 
} 

class SpriteA : Sprite { 
    public override TResult Invoke<TResult> (ISpriteInvoker<TResult> invoker){ 
     return invoker.Invoke(this); 
    } 
} 

class SpriteB : Sprite { 
    public override TResult Invoke<TResult> (ISpriteInvoker<TResult> invoker){ 
     return invoker.Invoke(this); 
    } 
} 

// Define Invoker/visit interface 
// Has to return a result to support the way it's used later (far from ideal 
// it would be nice if there was a way to pass 'void' as the result) 
interface ISpriteInvoker<TResult>{ 
    // note, one invoke overload for each type of supported sprite 
    TResult Invoke(SpriteA sprite); 
    TResult Invoke(SpriteB sprite); 
} 


// Define interface for hitter 

interface IHitter { 
    // Note, one overload for each type of sprite that can be hit 
    int Hit(SpriteA sprite); 
    int Hit(SpriteB sprite); 
} 

// Define some Hitter classes (one for each type of sprite that 
// can do the hitting). It would be nice if this could be 
// Hitter<TSprite>, however as previously stated, this won't work 
// because the compiler doesn't support where TSprite : (SpriteA or SpriteB) 
// at least not that I can find.. 

class SpriteAHitter : IHitter { 
    SpriteA _sprite; 

    public SpriteAHitter(SpriteA sprite) { 
     _sprite = sprite; 
    } 

    public int Hit(SpriteA sprite) 
    { 
     HitHelper.Hit(_sprite, sprite); 
     return 0; 
    } 

    public int Hit(SpriteB sprite) 
    { 
     HitHelper.Hit(_sprite, sprite); 
     return 0; 
    } 

} 

class SpriteBHitter : IHitter { 
    SpriteB _sprite; 

    public SpriteBHitter(SpriteB sprite) { 
     _sprite = sprite; 
    } 

    public int Hit(SpriteA sprite) 
    { 
     HitHelper.Hit(_sprite, sprite); 
     return 0; 
    } 

    public int Hit(SpriteB sprite) 
    { 
     HitHelper.Hit(_sprite, sprite); 
     return 0; 
    } 

} 

// Invoker that takes in a sprite and creates 
// the appropriate Hitter wrapper. 

class HitterCreator : ISpriteInvoker<IHitter> { 
    public IHitter Invoke(SpriteA sprite) { 
     return new SpriteAHitter(sprite); 
    } 

    public IHitter Invoke(SpriteB sprite) { 
     return new SpriteBHitter(sprite); 
    } 
} 

// Invoker that is constructed with a hitter 
// and uses it to kick off the appropriate collison 

class HitActioner : ISpriteInvoker<int> { 
    IHitter _hitter; 

    public HitActioner(IHitter hitter) { 
     _hitter = hitter; 
    } 

    public int Invoke(SpriteA sprite) { 
     return _hitter.Hit(sprite); 
    } 

    public int Invoke(SpriteB sprite) { 
     return _hitter.Hit(sprite); 
    } 
} 


// Class taken from question, processes the hits 
// currently just outputs what hit what... 

class HitHelper { 
    public static void Hit(SpriteA a, SpriteB b) { 
     Console.WriteLine("a hit b"); 
    } 

    public static void Hit(SpriteB b, SpriteA a) { 
     Console.WriteLine("b hit a"); 
    } 

    public static void Hit(SpriteB b, SpriteB b1) { 
     Console.WriteLine("b hit b1"); 
    } 
    public static void Hit(SpriteA a, SpriteA a1) { 
     Console.WriteLine("a hit a1"); 
    } 
} 


class Program { 
    // class for testing two members 
    class Collision { 
     public Sprite Hitter { get; set; } // sprite causing collision 
     public Sprite Receiver { get; set; } // sprite getting hit 
    } 

    static void Main(string[] args) { 
     // Define each type of collision (A->A, A->B, B->A, B->B) 
     Collision[] collisions = new Collision[] { 
      new Collision{Hitter=new SpriteA(), Receiver = new SpriteA()} , 
      new Collision{Hitter=new SpriteA(), Receiver = new SpriteB()} , 
      new Collision{Hitter=new SpriteB(), Receiver = new SpriteA()} , 
      new Collision{Hitter=new SpriteB(), Receiver = new SpriteB()} }; 

     // For each scenario, process the collision 
     foreach (var collision in collisions) { 
      // Create the appropriate hitter wrapper for the sprite doing the hit 
      var hitter = collision.Hitter.Invoke(new HitterCreator()); 

      // perform the collision action against the object that has been hit 
      var result = collision.Receiver.Invoke(new HitActioner(hitter)); 
     } 

     // Output: 
     // a hit a1 
     // a hit b 
     // b hit a 
     // b hit b1 
    } 

}