可以在 C# 中将变量的引用保存在另一个变量中吗?

It is possible to save a variable's reference inside another variable in c#?

我正在开发一款小型主机游戏,您可以在其中穿越地牢。我正在尝试为它制作一个药水系统,想法是当你使用药水时,它会改变玩家的特定属性。

玩家的统计数据存储在名为 Stats 的静态 class 中。我想从相同的 class 创建不同的药水,并使用其构造方法更改药水作用的统计数据。我希望它工作的方式是,当创建一个新的药水实例时,我将对 stat 变量的引用传递给构造函数,并且构造函数将该引用存储在一个变量中,以便在使用药水时使用它。

我尝试将 delegates 与 getter 和 setter 一起使用,但它不起作用,因为它们仅适用于函数。我可以通过制作药水 ID 系统或学习正确使用指针来解决这个问题,但我更喜欢只使用安全代码。

我的问题是:在c#中有一种方法可以将对一个变量的引用存储在另一个变量中吗?

Stats class:

static class Stats{
    public static int health = 10,
                      strenght = 5,
                      defense = 20;
}

药水class:

class Potion {
    int statRef; //This is the "reference holder" variable I was asking about.
    int magnitude;
        public Potion(ref int stat, int _magnitude)
        {
             magnitude = _magnitude;
             statRef = stat; //Here I want to save the reference to the stat to the "reference holder"
        }

        public void UsePotion()
        {
            statRef += magnitude; //Here I want to change the referenced variable's value.
        }

}

主程序:

class Program{
    static class Main(string[] args)
    {
       Potion lifePotion = new Potion(Stats.life, 5);
       Potion strenghtPotion = new Potion(Stats.strenght, 5);
       Potion defensePotion = new Potion(Stats.defense, 10);
      
       lifePotion.UsePotion();
       strenghtPotion.UsePotion();
       defensePotion.UsePotion();

      Console.WriteLine(Stats.health);
      Console.WriteLine(Stats.strenght);
      Console.WriteLine(Stats.defense);

    }
}

C# 是一种面向对象的编程语言。这意味着它旨在使用“对象”或 classes 的内存实例,它们负责维护自己的状态。现在你不必这样做,但是你越偏离这个设计,语言对你的支持就越少,你自己要做的工作就越多。

你的设计不是面向对象的。可能没有一个“统计数据”在您的游戏中四处游荡,统计数据。它可能也不是 static。静态 classes 用于无法更改的概念。例如Math.Sin就是static;它的意思不能改变,我的 Math.Sin 就是你的 Math.Sin.

您的游戏可能有角色或 Mook,而不是 static Stats 四处游荡。所以为他们做一个class:

public class Mook
{
    public string Name { get; }
    public int Strength { get; private set; }

    public Mook(string name, int strength)
    {
        Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentNullException(nameof(name)) : name;
        Strength = strength;
    }
}

现在您可以创建 Mooks 的实例:

var player = new Mook("Link", 10);
var monster = new Mook("Orc", 11);

Mooks 可以做一些事情,比如攻击或喝药水。药水可以做一些事情,比如改变你的力量。每个 class 只对自己的内部状态负责;您没有改变 Mooks 的药水,只有 Mook 自己可以做到这一点。 类做一些通过方法改变自己的事情。如果你想让 Mook 喝药水,你必须在你的 Mook 中创建一个方法 class 来做到这一点:

    public void Drink(Potion potion)
    {
        switch (potion.Sipped())
        {
            case PotionEffect.ModifyStrength:
                Strength += potion.Modifier;
                break;
        }
    }

药水无法决定自身之外发生的事情,只有 class 使用药水才能做到。要跟踪药水的可能效果,请创建一个枚举:

public enum PotionEffect
{
    Nothing,
    ModifyStrength
}

药水是其他物品,所以你需要再制作一个class。请记住,每个 class 负责维护自己的状态:

public class Potion
{
    public PotionEffect Effect { get; }
    public int Modifier { get; }
    public int Doses { get; private set; }

    public Potion(PotionEffect defaultEffect, int modifier, int doses)
    {
        Effect = defaultEffect;
        Modifier = modifier;
        Doses = doses;
    }

    public PotionEffect Sipped()
    {
        if (Doses <= 0)
            return PotionEffect.Nothing;

        Doses--;
        return Effect;
    }
}

现在你可以制作药水了:

var strengthPotion = new Potion(PotionEffect.ModifyStrength, +1, 10);

然后让 mooks 喝它们:

player.Drink(strengthPotion);
monster.Drink(strengthPotion);

请注意,class 是引用类型。因此,class 类型的变量自动包含引用,您可以将相同的引用分配给另一个变量。如果 class 不是静态的,您只能创建一个对象(即 class 的实例)。然后你可以把它赋值给一个变量。

字段或属性不能是静态的。非静态成员称为实例成员。每个实例(对象)都有自己的字段和属性副本,必须通过变量名访问这些副本。相反,静态成员在该类型的所有对象之间共享,并且必须通过 class 名称访问。

Stats stats1 = new Stats();
Stats stats2 = stats1;

现在两个变量引用相同的统计数据。如果你做出改变

stats1.health = 5;

then stats2.health 也是 5,因为两者都引用同一个对象。但当然,您可以创建独立的 Stats 个对象:

Stats stats1 = new Stats();
Stats stats2 = new Stats();

现在对 stats1 的更改不会影响 stats2


请注意,面向对象编程 (OOP) 的一个重要思想是对象应隐藏其内部状态,即它们的字段。从外部状态只能通过方法访问。这些方法确保以适当的方式操纵状态。例如,可以确保健康保持在有效范围内。

属性是允许操纵字段状态的专用方法。它们通常由一对 getset 方法组成,可以像字段一样访问。

示例:

class Stats
{
    private int _health = 10;
    public int Health
    {
        get { // called when reading the value: int h = stats1.Health;
            return _health;
        } 
        set { // called when setting the value: stats1.Health = 5;
            if (value < 0) {
                _health = 0;
            } else if (value > 100) {
                _health = 100;
            } else {
                _health = value;
            }
        }
    }
}

如果 属性 没有这样的逻辑,您可以使用自动实现的 属性。它会自动创建一个不可见的支持字段(如 _health)和 returns 并设置其值。

public int Health { get; set; }

让我们把这些东西放在一起。 Stats class的简单例子:

class Stats
{
    public int Health { get; set; } = 10;
    public int Strength { get; set; } = 5;
    public int Defense { get; set; } = 20;
}

现在您可以在 Potion class 中引用 Stats 对象。

因为你想拥有不同种类的药水,你可以使用继承(OOP 的另一个重要概念)来实现。

你可以声明一个抽象基础class,即一个不能被实例化并且本身可以包含抽象成员的class,即仍然需要在派生[=77中定义的成员=]es.

abstract class Potion
{
     // This is the "reference holder" variable you were asking about.
    protected Stats _stats;

     // Protected means private and visible to derived classes.
    protected int _magnitude;

    public Potion(Stats stats, int magnitude)
    {
        _stats = stats; // Save the reference to the stat to the "reference holder"
        _magnitude = magnitude;
    }

    public abstract void UsePotion();
}

现以派生LifePotionclass为例

class LifePotion : Potion // Inherits Potion.
{
    public LifePotion(Stats stats, int magnitude)
       : base(stats, magnitude) // Calls the base constructor.
    {
    }

    public override void UsePotion()
    {
        _stats.Health += _magnitude; // Change a property of the referenced variable.
    }
}

StrenghtPotionDefensePotion class 重复相同的操作,UsePotion 设置 StrengthDefense 属性。

适配主程序

class Program{
    static class Main(string[] args)
    {
        var stats = new Stats();
        Potion lifePotion = new LifePotion(stats, 5);
        Potion strenghtPotion = new StrengthPotion(stats, 5);
        Potion defensePotion = new DefensePotion(stats, 10);

        lifePotion.UsePotion();
        strenghtPotion.UsePotion();
        defensePotion.UsePotion();

        Console.WriteLine(stats.Health);
        Console.WriteLine(stats.Strength);
        Console.WriteLine(stats.Defense);
    }
}

请注意,您可以在 class 中覆盖 ToString 并提供您自己的实现。将此添加到 Stats class:

public override string ToString()
{
    return $"Health = {Health}, Strength = {Strength}, Defense = {Defense}";
}

然后你可以在主例程中像这样打印健康:

Console.WriteLine(stats); // Prints: Health = 15, Strength = 10, Defense = 30