使用反射在结构体和类之间复制相应属性

3
我是一个游戏助手(请参见http://haighshroom.blogspot.com了解更多信息),并使用XML文件保存/加载游戏。目前,每个类的保存格式都使用不同的结构体,但是继续更新保存文件结构变得繁琐,因此我想尝试采用以下通用方法。
假设我有3个类,它们都继承自BaseClass:
Class1,它有Property1、Property2(假设所有属性都是整数)
Class2,它有Property2、Property3
Class3,它有Property1、Property3
现在,我的新通用SaveStruct将如下所示:
public struct EntityStruct
{
    public string ClassName;
    public int Property1;
    public int Property2;
    public int Property3;
    public EntityStruct()
    {
        ClassName = "";
        Property1 = Property2 = Property3 = 0;
    }
}

当保存/加载特定实体时,我想要实现以下伪代码(两个函数都从BaseClass调用):

public EntityStruct GetSaveStruct()
{
    EntityStruct es = new EntityStruct();
    es.ClassName = this.GetType().Name;
    if Exists(this.Property1) es.Property1 = Get(this.Property1);
    if Exists(this.Property2) es.Property2 = Get(this.Property2);
    if Exists(this.Property3) es.Property3 = Get(this.Property3);
}
public void LoadFromStruct(EntityStruct es)
{
    BaseClass e = (BaseClass)(Activator.CreateInstance(null, GV.NameSpace + es.ClassName).Unwrap());
    if Exists(e.Property1) Set(e.Property1 = es.Property1);
    if Exists(e.Property2) Set(e.Property2 = es.Property2);
    if Exists(e.Property3) Set(e.Property3 = es.Property3);
}

我不知道如何定义以下部分:
- Exists(e.Property1) - 这需要使用反射来确定实例e是否已定义Property1(由于我们是从BaseClass调用,因此需要使用Reflection)。
- Get(e.Property1) - 如果Property1确实存在于实例e中,则需要获取它的值。
- Set(e.Property1 = es.Property1) - 如果实例e存在Property1,则需要设置其值。
非常感谢。

2
你可以选择使用反射方法,或者使用像 AutoMapper 这样的库来完成这个任务。如果你的属性命名约定相同,使用 DynamicMap 可能不需要任何配置就可以顺利运行。 - Charleh
或者使用Json.Netvar newObj = JsonConvert.DeserializeObject<SomeOtherClass>(JsonConvert.SerializeObject(yourObject)); - L.B
Automapper看起来很有趣,我已经收藏了它以备后用,但现在我正在使用ShallowCopy方法。谢谢大家。 - Haighstrom
2个回答

4
你可以尝试使用这个代码。
public void ShallowCopyValues<T1, T2>(T1 firstObject, T2 secondObject)
{
    const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
    var firstFieldDefinitions = firstObject.GetType().GetFields(bindingFlags);
    IEnumerable<FieldInfo> secondFieldDefinitions =     secondObject.GetType().GetFields(bindingFlags);

    foreach (var fieldDefinition in firstFieldDefinitions)
    {
        var matchingFieldDefinition = secondFieldDefinitions.FirstOrDefault(fd => fd.Name == fieldDefinition.Name &&
                                                                                  fd.FieldType == fieldDefinition.FieldType);
    if (matchingFieldDefinition == null)
        continue;

        var value = fieldDefinition.GetValue(firstObject);
        matchingFieldDefinition.SetValue(secondObject, value);
    }
}

我最终使用了这种方法,感谢您的帮助。我将另一个答案标记为已接受,仅因为它回答了问题。 - Haighstrom
我在尝试让这个工作时遇到了麻烦。字段从未匹配过。这可能是因为其中一些具有get/set访问器,或者因为BaseClass没有定义它正在搜索的任何属性吗?(我正在调用ShallowCopyValues<BaseClass,EntityStruct>,但我需要从Class1:BaseClass复制的字段) - Haighstrom
是的,这似乎跳过了所有的get/set方法,只关注BaseClass和更高级的类。我觉得这不太适合,因为我不能在编译时写入类名。 - Haighstrom

3
所有这些的起点是System.Type类。您可以使用e.GetType()获取此类型的实例。
要查找字段,请使用GetField。如果返回null,则该字段根本不存在。
如果它返回一个值(类型为FieldInfo),则使用GetValue获取该值,并使用SetValue进行设置。
反射相对较慢,因此如果性能是问题,可以使用诸如System.Type.getType(name)之类的方法提前获取System.Type对象,并获取FieldInfo对象。您不需要类的实际实例来执行这两个操作,但是显然需要它来获取和设置字段值。

我将此标记为已接受的答案,因为它实际上回答了我的问题,尽管最终我使用了ShallowClone方法。 - Haighstrom

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