在运行时使用C#合并两个对象的最佳方法是什么?

6

我有两个对象,我想将它们合并:

public class Foo
{
    public string Name { get; set; }
}

public class Bar
{
    public Guid Id { get; set; }
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public string Property3 { get; set; }
    public string Property4 { get; set; }
}

创建:
public class FooBar
{
    public string Name { get; set; }
    public Guid Id { get; set; }
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public string Property3 { get; set; }
    public string Property4 { get; set; }
}

我只能在运行时获得Foo的结构。Bar在运行时可以是任何类型。我想要一个方法,它将给定的类型与Foo组合起来。例如,在上面的场景中,该方法在运行时被赋予了一个Bar类型,并将其与Foo组合在一起。
最好的方法是什么?可以使用LINQ表达式来实现吗?还是必须动态生成?或者有其他方法?我仍在学习C# 3.0中的新LINQ命名空间,所以如果不能使用LINQ表达式,请原谅我的无知。这也是我第一次在C#中做像这样动态的事情,所以我不太确定所有可用的选项。
感谢提供的任何选项。
编辑
这仅适用于向给定给我的类型添加元信息,以进行序列化。在进行序列化之前,此方案使用户的对象不了解需要添加的元信息。在询问此问题之前,我想出了两个选项,我只是想看看是否还有更多选项,然后再决定使用哪一个。
我想出的两个选项是:
通过添加元信息操作我得到的类型的序列化字符串。
封装给我的类型,这类似于Zxpro提到的,但我的略有不同,这没关系。这只会让我的API的用户遵循约定,这不是坏事,因为人人都追求约定优于配置。
public class Foo<T>
{
    public string Name { get; set; }
    public T Content { get; set; }
}

编辑


感谢大家的回答。我决定像上面那样包装对象,然后将答案给了@Zxpro,因为大多数人也喜欢这种方法。

如果有其他人遇到这个问题,请随时发布,如果您认为可能有更好的方法。

7个回答

8

如果您不介意它们被分组而不是合并:

public class FooEx<T>
{
    public Foo Foo { get; set; }
    public T Ex { get; set; }
}

1
这将是一个理想的解决方案,但它需要编译时对 Bar 的了解,而原始提问者已经说明此信息只能在运行时获取。 - Randolpho
@Randalpho:也许是,也许不是。在那种情况下可能不为人知,但根据架构,使用通用程序可能是可行的。 - Adam Robinson
我考虑过这个问题,这是我的备选计划,如果无法轻松高效地完成。我的方案略有不同。基本上是将Foo<T>公开 { public string Name { get; set; } public T Content { get; set; } } - Dale Ragan
不一定,具体取决于OP需要什么细节,可能在某些场景下可以使用typeof(FooEx<>).MakeGenericType( ).CreateInstance( )。 - Doug McClean

3

未经测试,但使用Reflection.Emit API,类似以下代码应该可以工作:

public Type MergeTypes(params Type[] types)
{
    AppDomain domain = AppDomain.CurrentDomain;
    AssemblyBuilder builder = 
        domain.DefineDynamicAssembly(new AssemblyName("CombinedAssembly"),
        AssemblyBuilderAccess.RunAndSave);
    ModuleBuilder moduleBuilder = builder.DefineDynamicModule("DynamicModule");
    TypeBuilder typeBuilder = moduleBuilder.DefineType("CombinedType");
    foreach (var type in types)
    {
        var props = GetProperties(type);
        foreach (var prop in props)
        {
            typeBuilder.DefineField(prop.Key, prop.Value, FieldAttributes.Public);
        }
    }

    return typeBuilder.CreateType();


}

private Dictionary<string, Type> GetProperties(Type type)
{
    return type.GetProperties().ToDictionary(p => p.Name, p => p.PropertyType);
}

用法:

Type combinedType = MergeTypes(typeof(Foo), typeof(Bar));

感谢您提供的代码,我很感激您的贡献。我一直知道Reflection.Emit是一个可行的解决方案,但我希望还有其他方法。如果包装类型不起作用,我一定会记住这个方法的。 - Dale Ragan

2

不幸的是,这不是一件容易做到的事情。最好的方法是在LINQ查询中创建一个匿名类型,但这只有本地作用域,因此仅在创建它的方法中有效。

当.NET 4发布时,将会有一个新的动态运行库,可能会对你有所帮助。


我一定会在有机会的时候去了解C# 4中的新动态特性。 - Dale Ragan

1

除了问题“为什么”,我所能想到的将两个对象(一个已知,一个未知)合并成新类型的唯一方法是使用Reflection.Emit在运行时生成新类型。

MSDN上有示例。您需要确定是否要合并具有相同名称的字段或使已知类型取代未知类型。

据我所知,LINQ无法实现此操作。

由于您感兴趣的只是属性,因此可以很容易地使用this article作为示例。省略创建方法的il即可。


“Why”的原因是为了将数据序列化成一种类型以进行持久存储。这样可以避免像我在对Zxpro的评论中所说的那样嵌套对象。 - Dale Ragan
为什么不先将第一个序列化,然后再将另一个序列化到同一个流/任何其他地方呢? - Anton Tykhyy
@Anton Tykhyy - 这是我使用过的其中一种选项。我只是在寻找其他可能更好的东西,而不是在我的情况下操纵字符串。 - Dale Ragan
我认为如果在编译时无法完全了解Foo和Bar,那么操作字符串比任何合理的替代方案都要好得多。 - Anton Tykhyy

0

正如其他人所指出的那样,在 SQL 中使用多个表进行 select * 等操作时,是没有办法将它们“合并”的。你最接近的类比是采用 Zxpro 提供的方法,在一个通用类中“分组”它们。

不过,你想通过“合并”它们来实现什么呢?明确声明属性对编写代码和编译时安全性有最大的便利效果,但如果你不能指定类型,那就没有机会了。如果你只是在寻找一个通用的“属性包”容器,那么现有的数据结构,例如 Dictionary<T,T>Hashtable 应该能够处理。


0
如果您可以向元数据类添加一个方法,那么您可以执行以下操作:
public class Foo
{
    public string Name { get; set; }
    public void SerializeWithMetadata(Bar bar)
    {
       var obj = new {
                       Name = this.Name,
                       Guid = bar.Guid,
                       Property1 = Bar.Property1
                      }
       //Serialization code goes here
    }
}

public class Bar
{
    public Guid Id { get; set; }
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public string Property3 { get; set; }
    public string Property4 { get; set; }
}

我不确定我会推荐这种确切的方法,主要是为了展示匿名类型作为一个可能值得探索的选项而留在这里。


问题在于在运行时之前无法确定Foo或Bar,而您的代码依赖于知道这些信息。 - Richard Anthony Freeman-Hein
我无法理解对象类型在运行时如何是未知的(这意味着它们没有被实例化),但是如果你的意思是并非所有 foo 和 bar 的组合在编译时都已知(或者数量太大),那么结果是相同的。 - Rune FS
在运行时之前,既不了解Foo也不了解Bar。我的意思是,您在编译时假设Bar具有Guid和Property1,并且发布者特别要求如何合并在编译时未知的类型。 - Richard Anthony Freeman-Hein

0

另一种选择:

将第一个类修改如下

public class Foo
{
    public string Name { get; set; }

    [System.Xml.Serialization.XmlAnyElementAttribute()]
    public XmlElement Any {get;set;}
}

将第二个类序列化为一个 XmlElement,方法如下:
XmlElement SerializeToElement(Type t, object obj)
{
    XmlSerializer ser = new XmlSerializer(t);
    StringWriter sw = new StringWriter();
    using (XmlWriter writer = XmlWriter.Create(sw, settings))
        ser.Serialize(writer, obj);

    string val  = sw.ToString();

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xmlString);

    return (XmlElement)doc.DocumentElement;

}

将属性Any设置为XmlElement,并对其进行序列化,您将在文档中嵌入其他类的XML,包括所有命名空间

即使使用Xsd.exe生成Class,如果使用以下元素,您也可以这样做:

<xs:any namespace="##any" processContents="lax" />

我相信你也可以使用一个 XmlElements 数组来处理多个子类。

反序列化应该是检查 XmlElements 然后查找匹配的类,或者使用命名空间来查找类。

这比搞字符串操作要整洁得多。


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