.net通用错误?

时间:2011-04-08 13:01:11

标签: .net generics design-patterns visitor-pattern

我找到了一个奇怪的泛型和重载方法的行为。看起来 使用泛型,重载机制不起作用:

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 个答案:

答案 0 :(得分:4)

我猜你看到所有使用泛型的东西都被视为B的一个实例(这是我期望的)。

您将通用类型T约束为B。这意味着无论您使用哪种类型的泛型参数,在确定在编译时调用哪个重载时,它都将被视为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(); }
}

答案 1 :(得分:1)

编辑:在对问题进行各种更改后重写答案

正如已经说过的那样(参见Justin),你不能只使用泛型,因为编译器需要在编译时知道支持的对象类型是什么。通过使用泛型,你仍然不清楚你想要达到的目标。如果是为了节省打字,那么这可能不是你问题的解决方案......

那就是说,我认为你的问题是你有两个未知类型的精灵(可能来自一个集合或其他东西):

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的类型,您尝试使用访问者模式执行这些操作并且它不起作用。这是因为您一次只能解析一个参数(您可以识别a或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
    }

}