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

时间:2009-08-31 18:55:41

标签: c# linq linq-expressions

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

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 个答案:

答案 0 :(得分:8)

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

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

答案 1 :(得分:3)

UNTESTED,但使用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));

答案 2 :(得分:2)

不幸的是,这不是你可以轻易做到的事情。您可以做的最好的事情是创建一个匿名类型作为LINQ查询的一部分,但它只有本地作用域,因此只有在您创建它的方法中才有用。

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

答案 3 :(得分:1)

除了“为什么”这个问题之外,我可以考虑采用两个对象,一个已知对象和一个未知对象,并将它们组合成一个新类型的唯一方法是使用Reflection.Emit在运行时生成一个新类型。

MSDN上有一些例子。您必须确定要合并具有相同名称或已知类型的字段取代未知类型的天气。

据我所知,在LINQ中无法做到这一点。

由于您感兴趣的是属性,因此使用this article作为示例非常容易。遗漏了用于创建方法的il,你很高兴。

答案 4 :(得分:0)

正如其他人所指出的那样,没有办法“合并”它们(例如,如果你想在SQL中有多个表的select *)。你最接近的模拟将采用Zxpro提供的路线,并将它们“分组”在一个通用类中。

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

答案 5 :(得分: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; }
}

我不确定我会推荐这种确切的方法我主要将它留在这里以显示匿名类型作为可能值得探索的可能选项

答案 6 :(得分: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生成类,如果您将以下内容用作元素,则甚至可以使用此方法:

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

我相信你也可以为一个以上的子类逃脱一系列XmlElements。

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

这比搞乱字符串操作更加整洁。