如何在C#中从类对象数组中访问属性?

4
    Character[] PlayerOne = new Character[5];

    PlayerOne[1] = new BladeWarrior();
    PlayerOne[2] = new FistWarrior();
    PlayerOne[3] = new Archer();
    PlayerOne[4] = new RedMage();
    PlayerOne[5] = new BlueMage();  

我知道通过多态性,BladeWarrior可以成为一个Character,但反过来不行。我的问题是,当我尝试访问数组的一个元素时,例如Player[1],我无法访问BladeWarrior类的函数和变量。它只允许我访问从Character类继承的变量和函数。
如果我想让两个角色战斗,我需要能够访问Bladewarrior的函数/变量。
我考虑使用“as”函数将PlayerOne[1]设置为特定对象。不完全像这样:
string s = objArray[i] as string;

以上代码行仅用于明确我所说的“as”的含义。
这个问题有什么解决方案?

你也可以根据父列表拥有个人列表,例如 PlayerOne.OfType<BladeWarrior>().ToArray(),以及其他类似的操作。 - nawfal
6个回答

2
"如果我想让两个角色战斗,我需要能够访问Bladewarrior的函数/变量。"
看起来你正在尝试进行多次分派:你想调用...
Fight(PlayerOne[i], PlayerOne[j]);

调用一个函数,该函数知道两个字符的确切类型。有不同的技巧可以在单分派语言中实现双重分派,最显著的是访问者模式。从C#4开始,您还可以使用dynamic以相对清晰和易于阅读的方式实现双重分派:
static class FightArena {
    public static void Fight(dynamic a, dynamic b) {
        try {
            DoFight(a, b);
        } catch {
            Console.Error.WriteLine("{0} and {1} cannot fight", a, b);
        }
    }
    private static void DoFight(BladeWarrior a, Archer b) {
    }
    private static void DoFight(BladeWarrior a, FistWarrior b) {
    }
    private static void DoFight(BladeWarrior a, RedMage b) {
    }
    private static void DoFight(BladeWarrior a, BlueMage b) {
    }
    private static void DoFight(BladeWarrior a, BladeWarrior b) {
    }
    private static void DoFight(Archer a, Archer b) {
    }
    ... // Enumerate all pairs that can fight
}

现在你可以写出这样的东西:
FightArena.Fight(PlayerOne[i], PlayerOne[j]);

并且根据PlayerOne[i]和PlayerOne[j]的动态类型,该调用将到达精确的类型对。

因此,基于Fight(dynamic a, dynamic b)中的参数,我们让程序知道,“嘿,我们需要2个参数,但它们可以是动态的”。我们通过该参数传递两个主要角色,然后在该函数内部启动战斗。由于我们有多个DoFight函数,程序将会选择哪个动态参数被抛入参数中,然后使用相应的DoFight函数? - user3134679
@user3134679 是的,这就是将要发生的事情。由于ab都被声明为dynamic,在选择正确的DoFight方法重载时将考虑运行时类型。 - Sergey Kalinichenko

1
如果您必须将您的Character的背景转换为它们的具体类型来进行战斗,则会失去多态性带来的任何好处。如果您需要将一个Character强制转换为Bladewarrior来进行战斗,那么这意味着您必须编写不同的逻辑才能使每个角色互相战斗。然后,如果您稍后添加了新的角色类型,则必须更新所有战斗代码以支持该类型。
您真正想要做的是编写一个通用的战斗算法,可以用于战斗任何两个Character对象而无需进行转换。您可以向Character基类添加属性,指示角色具有的属性。
例如,您可以向Character类添加一个Attacks属性,该属性是角色拥有的攻击列表,其中包含攻击名称和造成的伤害量。然后,每个派生的角色类都将填充其Attacks列表,并且您的战斗算法将处理这些攻击。通过这种方式,每个角色都可以拥有自己的攻击列表,但不必转换为特定的角色类型才能访问它。
这里的最终目标是让任何外部代码只知道Character类,而不知道它的任何派生类。这将使您的外部代码更加清晰,并使您能够在未来添加或删除字符类。

0

如果你想要访问BladeWarrior的属性,将你的Character转换为BladeWarrior是完全正常的,而且你可以使用as关键字来实现:

BladeWarrior bw = PlayerOne[1] as BladeWarrior;

谢谢。我已经添加了那行代码到我已经编写的代码下面。我可以通过以下方式访问函数/变量:bw.normalattack();但是我无法通过以下方式访问它:PlayerOne[1].normalattack(); - user3134679

0

你正在进行向上转型,因此你只能使用parent类的行为和属性。

我认为你需要为所有实例单独执行此操作。

BladeWarrior player1 = new BladeWarrior();
FistWarrior player2 = new FistWarrior();
Archer player3 = new Archer();
//and so on

我使用数组的原因是在初始化战斗实例时,我可以将数组作为参数传递到“fight”选项中,然后在该函数中确定正在袭击/造成伤害的角色类型。有更好的方法吗?也许使用重写函数,每个角色类型一个函数?像这样:public void normal attack(ref Bladewarrior BW) public void normal attack(ref Archer arch)然后让程序确定要使用哪个函数? - user3134679
@user3134679 我认为dasblinkenlight的答案是你情况下更好的解决方案。 - Shaharyar
他的答案是否仍然使用了多态性? - user3134679

0

正如你所说,你可以使用 "as" 进行强制类型转换。如果你要转换的实例无法转换为目标类,则会返回 null。

var currentCharacter = PlayerOne[1] as BladeWarrior;
if(currentCharacter  != null)
{
....
}

这里的挑战是找到一种清晰的方式来确定正确的类型转换。 也许您可以在数组中使用一个结构体,并使用标志来指示底层类。

switch(PlayerOne[1].Type)
{
    case PlayerTypes.BladeWarrior:
    currentCharacter = PlayerOne[1].Character as BladeWarrior;
}

但总的来说,似乎您没有遵循Liskov替换原则(SOLID原则中的L)。您不应该需要访问特定字符类型的实现细节,只需覆盖一些方法或使用基于策略模式的更模块化(和复杂)的设计即可。


0

或者您可以使用接口

public interface ICharacter {
    int myValue { get; set;}
    void myMethod();
}

public class BladeWarrior : ICharacter {
    private int myPrivateValue;
    public int myValue { get { return myPrivateValue; } set { myPrivateValue = value; } }

    public void myMethod() {
    //Do what you want
    }
}


ICharacter[] PlayerOne = new ICharacter[5];
PlayerOne[0] = new BladeWarrior();

然后您可以访问您的接口方法 ICharacter[0].myMethod();


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接