2009-08-31 8 views
6

J'ai deux objets et je veux les fusionner:Quel est le meilleur moyen de fusionner deux objets pendant l'exécution en utilisant C#?

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; } 
} 

Pour créer:

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; } 
} 

Je ne connaître la structure de Foo lors de l'exécution. La barre peut être de n'importe quel type à l'exécution. Je voudrais avoir une méthode qui va recevoir un type et combiner ce type avec Foo. Par exemple, le scénario ci-dessus, la méthode a été donné un type de barre à l'exécution et je l'ai combiné avec Foo.

Quelle serait la meilleure façon de faire cela? Peut-il être fait en utilisant des expressions LINQ ou dois-je le générer dynamiquement ou y a-t-il un autre moyen? J'apprends toujours le nouvel espace de noms LINQ dans C# 3.0, donc excusez l'ignorance si cela ne peut pas être fait en utilisant des expressions LINQ. C'est aussi la première fois que je fais quelque chose de dynamique avec C#, donc je ne suis pas sûr de toutes les options dont je dispose.

Merci pour les options indiquées.

EDIT


Ceci est strictement pour ajouter des méta-informations du type qui m'a été donnée pour la sérialisation. Ce scénario empêche les objets de l'utilisateur d'ignorer les métadonnées qui doivent être ajoutées avant d'être sérialisées. J'ai trouvé deux options avant de poser cette question et je voulais juste voir s'il y en avait plus, avant de décider lequel utiliser.

Les deux options que je suis venu avec sont:

la chaîne sérialisée Manipuler du type qui m'a été donné après la sérialisation il, en ajoutant les méta-informations.

Envelopper le type donné à moi, qui est semblable à ce que @Zxpro mentionné, mais le mien différait légèrement, ce qui est bien. Il va juste faire l'utilisateur de mon API doivent suivre la convention, ce qui est une mauvaise chose, puisque tout le monde est sur le point sur la configuration convention:

public class Foo<T> 
{ 
    public string Name { get; set; } 
    public T Content { get; set; } 
} 

EDIT


Merci à tous pour leurs réponses. J'ai décidé d'emballer l'objet comme ci-dessus et j'ai donné la réponse à @ Zxpro, car une majorité a aussi aimé cette approche.

Si quelqu'un d'autre rencontre cette question, n'hésitez pas à poster, si vous pensez qu'il pourrait y avoir un meilleur moyen.

Répondre

8

Si vous ne les dérange pas d'être regroupés plutôt que fusionné:

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

Ce serait une solution idéale, mais cela nécessiterait une connaissance à la compilation de Bar, que le demandeur d'origine a déclaré être uniquement disponible au moment de l'exécution. – Randolpho

+0

@Randalpho: Peut-être, peut-être pas. Il peut ne pas être connu * dans ce contexte *, mais il pourrait bien être possible, selon l'architecture, d'utiliser un générique. –

+0

J'ai pensé à cela et c'est mon plan de repli, si cela ne peut pas être fait facilement et performant. La mienne différait légèrement cependant. Fondamentalement rendre public Foo {chaîne publique Name {get; ensemble; } public T Content {get; ensemble; }} –

2

Malheureusement, ce n'est pas quelque chose que vous pouvez faire facilement. Le mieux que vous pouvez faire est de créer un type anonyme dans le cadre d'une requête LINQ, mais cela aura local portée uniquement, et ne sera donc bon pour vous dans la méthode dans laquelle vous le faites.

Lorsque .NET 4 sort, une nouvelle bibliothèque Dynamic Runtime peut vous aider.

+0

Je vais certainement vérifier les nouvelles choses dynamiques dans C# 4 quand j'ai l'occasion. –

1

En dehors de la question « Pourquoi », la seule façon que je peux penser à prendre deux objets, l'un connu et un inconnu et les combiner dans un nouveau type consisterait à utiliser Reflection.Emit pour générer un nouveau type lors de l'exécution.

Il existe des exemples sur MSDN. Vous devez déterminer le temps que vous vouliez fusionner les champs portant le même nom ou dont le type connu remplace le type inconnu. Dans la mesure où je peux dire, il n'y a aucun moyen de le faire dans LINQ.

Puisque tout ce qui vous intéresse est Propriétés, il devrait être assez facile à utiliser this article comme exemple. Laissez tomber les méthodes de création et vous êtes prêt à partir.

+0

Le "Pourquoi", est pour la sérialisation dans un type pour le stockage persistant. Cela m'empêcherait d'avoir à imbriquer les objets, comme dans mon commentaire à Zxpro. –

+0

Pourquoi ne pas simplement sérialiser l'un, puis l'autre dans le même flux? –

+0

@Anton Tykhyy - C'est l'une des options que j'ai utilisées. Je cherche juste autre chose qui pourrait être meilleur, au lieu de manipuler des cordes, dans mon cas. –

0

Comme d'autres l'ont souligné, il n'y a aucun moyen de les "fusionner" (si vous songez à un select * avec plusieurs tables en SQL, par exemple). Votre analogue le plus proche serait de prendre la route que Zxpro a fourni et de les "grouper" dans une classe générique. Mais que voulez-vous exactement accomplir en les "fusionnant", par contre? Déclarer explicitement les propriétés aurait le plus grand effet pratique sur l'écriture de code et la sécurité au moment de la compilation, mais si vous ne pouvez pas spécifier un type, alors il n'y a aucune opportunité pour cela. Si vous recherchez simplement un conteneur générique "sac de propriétés", une structure de données existante, telle qu'un Dictionary<T,T> ou Hashtable devrait être capable de gérer cela.

3

NON TESTÉ, mais en utilisant l'API Reflection.Emit, quelque chose comme cela devrait fonctionner:

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); 
} 

USAGES:

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

Merci pour le code, j'apprécie votre contribution. J'ai toujours su que Reflection.Emit était une solution viable, mais j'espérais qu'il y aurait d'autres façons. Je vais certainement garder cela à l'esprit, si l'emballage du type ne fonctionne pas. –

0

Si vous pouvez ajouter une méthode à la classe de métadonnées que vous pouvez faire quelque chose comme le suivant

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; } 
} 

Je ne suis pas sûr que je recommanderais cette approche exacte je suis parti principalement ici pour afficher des types anonymes comme une option possible qui pourrait être la peine d'explorer

+0

Le problème est que ni Foo ni Bar sont connus avant l'exécution, et votre code dépend de la connaissance de cette information. –

+0

Je ne vois pas comment le type d'objets peut être inconnu à l'exécution (ce qui veut dire qu'ils ne sont pas instanciés) mais si vous ne comprenez pas toutes les combinaisons de foo et de bar à _compile time_ (ou le nombre est trop grand) le résultat est le même –

+0

"ni Foo ni Bar n'est connu avant l'exécution" ... ce que je voulais dire c'est que vous avez supposé que Bar a un Guid et Property1, au moment de la compilation, et l'affiche a demandé spécifiquement comment fusionner les types qui sont inconnus au moment de la compilation. –

0

Une autre option:

Modifier la première classe comme si

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

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

Prenez la deuxième classe et sérialisation en un XmlElement comme si:

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; 

} 

Set Quelque au XmlElement et sérialiser Vous verrez le XML pour l'autre classe emb edded dans le document, avec tous les espaces de noms

Vous pouvez même obtenir avec si la classe a été générée à l'aide Xsd.exe si vous utilisez ce qui suit comme un élément:

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

Je crois que vous pouvez Également obtenir un tableau de XmlElements pour plus d'une sous-classe.

La déserialisation devrait être une question d'inspecter les XmlElements, puis de rechercher une classe correspondante, ou peut-être d'utiliser l'espace de noms pour trouver la classe.

Ceci est beaucoup plus ordonné que de jouer avec la manipulation de chaînes.