我找到了一个奇怪的泛型和重载方法的行为。看起来 使用泛型,重载机制不起作用:
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
}
}
答案 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
}
}