我有2个返回IEnumerable<Stat>的函数。

此函数返回属于CharacterStats类的所有单个属性:

private IEnumerable<Stat> GetAllStats()
{
    yield return Level;
    yield return Health;
    yield return Damage;
    yield return Defense;
    //several others
    yield return LifePerSecond;
}


此函数从我新添加的sqlite数据库获取存储的(基本)值:

private IEnumerable<Stat> GetBaseStatsFromDB()
{
    //connection code ...

    while (rdr.Read())
    {
        yield return new Stat
        {
            Name = (Enums.StatName)Enum.Parse(typeof(Enums.StatName), rdr.GetString(1)),
            MinValue = rdr.GetInt32(2),
            MaxValue = rdr.GetInt32(3),
            CurrentValue = rdr.GetInt32(4)
        };
    }

    //close connection
}


我想做的是返回CharacterStats的新实例,其中每个Stat是从数据库初始化的,并分配给该实例,例如:

GetAllStats.ToList().ForEach( //apply matching stat from GetBaseStatsFromDB());


我能够从数据库成功获取值,并从每个值中创建一个新的Stat对象。但是,我一直无法将这些值应用于CharacterStats类。

目前大约有16种统计数据,随着我继续构建此项目,这些统计数据可能会被添加/删除,因此理想情况下,我想找到一种方法,该方法将随着此列表的更改而继续起作用。

如何使用GetBaseStatsFromDB()方法将CharacterStats的结果应用于GetAllStats()的所有属性?

编辑澄清:

CharacterStats是一个类,其中包含所生成的不同Stat。例:

public class CharacterStats
{
    public Stat Level;
    public Stat Name;
    ...
    public Stat LifePerSecond;
}


GetAllStats()是用于一次枚举每个属性的函数(如列表)

GetBaseStatsFromDB()是我尝试从数据库初始化这些值的尝试。我想做的是在CharacterStats上获取每个属性,并从GetBaseStatsFromDB()应用匹配的统计信息。

所以... GetBaseStatsFromDB()会返回(除了其他值):

Stat
{
    Name = "Level",
    MinValue = 1,
    MaxValue = 50,
    CurrentValue = 1
}


我想获取此结果并将其应用于Stat:Level类的CharacterStats属性,然后对所有其他统计信息重复此操作。

最佳答案

在这里,反思是显而易见的答案(并非唯一的答案)。

首先,确保Enums.StatName的值具有与CharacterStats上的属性名称相同的名称(我想您还是这么做了,因为很明显)。

//  Instance method of CharacterStats
public void SetStats(IEnumerable<Stat> stats)
{
    var cstype = this.GetType();
    foreach (var stat in stats) {
        var prop = cstype.GetProperty(stat.Name.ToString());
        prop.SetValue(this, stat);
    }
}

//  Toss in a constructor too.
public CharacterStats(IEnumerable<Stat> stats)
{
    SetStats(stats);
}

//  And a factory method
public static FromStats(IEnumerable<Stat> stats)
{
    return new CharacterStats(stats);
}


像这样使用:

var stats = new CharacterStats(GetBaseStatsFromDB());
var stats2 = CharacterStats.FromStats(GetBaseStatsFromDB());

//  later on, maybe you want to copy stats from one to another...
stats.SetStats(stats2.GetStats());


但是,如果我们可以共享Stat实例(此代码中的任何内容都不能阻止它),则在复制Stat实例时将它们克隆会更安全:

public Stat() { ... }
public Stat(Stat copyMe)
{
    this.Name = copyMe.Name;
    this.MinValue = copyMe.MinValue;
    this.MaxValue = copyMe.MaxValue;
    this.CurrentValue = copyMe.CurrentValue;
}


...然后...

//  This version is much safer.
public void SetStats(IEnumerable<Stat> stats)
{
    var cstype = this.GetType();
    foreach (var stat in stats) {
        var prop = cstype.GetProperty(stat.Name.ToString());
        prop.SetValue(this, new Stat(stat));
    }
}


@PanagiotisKanavos注意到(至少基于我们所看到的),Enums.StatName枚举根本没有必要。如果仅对Name使用字符串,则可以在数据库中引入新的统计信息,而无需更改枚举。

如果您使用枚举来避免“魔术字符串”(也许某些统计信息在硬编码引用中使用名称来引用),那么对于静态检查和IntelliSense,枚举是正确的做法。但是,您可能需要考虑为代码需要通过名称知道的统计信息保留一个枚举或只读全局变量,然后再从数据库中接受在代码中没有任何特殊状态的统计信息中的任意字符串名称。那是您今天不需要穿过的一座桥梁。

Panagiotis还指出,您可以使CharacterStatsDictionary<String, Stat>的子类,并且完全避免弄乱反射。这不是一个坏主意。一个缺点是魔术字符串会再次出现:例如,如果每个字符都有LevelDamage等,并且代码专门与Level交互,则将Level设为类属性是明智的。

关于c# - 通过命名值的IEnumerable <T>在实例上设置属性值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/36289775/

10-09 02:26