.net общие ошибки? - PullRequest
       29

.net общие ошибки?

1 голос
/ 08 апреля 2011

Я нашел странное поведение для универсального и перегрузочного метода. Кажется, что с дженериками механизм перегрузки не работает:

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();
        }
    }
}

Есть ли тело, которое может это объяснить? Есть ли обходной путь.

То, что я пытаюсь сделать, это реализовать универсальный класс посетителя (шаблон посетителя) с универсальным. ТИА

Добавлено:

Вопрос, который я действительно пытаюсь решить, - это проблема двойной отправки. Предположим, вы хотите написать небольшой игровой движок, в котором вы должны обрабатывать столкновения между спрайт:

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 ]

4 голосов
/ 08 апреля 2011

Я предполагаю, что вы видите все, используя дженерики, обработанные так, как если бы это был экземпляр 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 голос
/ 08 апреля 2011

РЕДАКТИРОВАТЬ: переписать ответ после различных изменений в вопросе

Как уже было сказано (см. Джастин), вы не можете просто использовать дженерики, потому что компилятору необходимо знать во время компиляции, какие типы объектов поддерживаются. Также до сих пор не ясно, чего вы пытаетесь достичь с помощью генериков. Если это сохранить набор текста, то это, вероятно, не решение вашей проблемы ...

Тем не менее, я думаю, что ваша проблема в том, что у вас есть два спрайта неизвестного типа (возможно, из коллекции или чего-то еще):

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
    }

}
...