C # - Concatenating a list of objects into one object

A list of small objects is passed to me:

var smalls = new List<Small>();
smalls.AddRange( new Small[] { new Small{Name = "Aa", Id = 1, Value = "v1"},
                               new Small{Name = "Bb", Id = 1, Value = "v2"},
                               new Small{Name = "Cc", Id = 1, Value = "v3"},
                               new Small{Name = "Dd", Id = 1, Value = "v4"},
                               new Small{Name = "Ee", Id = 1, Value = "v5"},
                               new Small{Name = "Ff", Id = 1, Value = "v6"},
                               new Small{Name = "Gg", Id = 1, Value = "v7"} } );

      

From the above list, I would like to populate an object that looks like this:

var large = new Large
    {
        Id = 1,
        Aa = "v1",
        Bb = "v2",
        Cc = "v3",
        Dd = "v4",
        Ee = "v5",
        Ff = "v6",
        Gg = "v7"
    }

      

The current code depends on how the list of list populates the Large object, however it is not safe enough and is looking for a more reliable way to map the list to the object.

Current code:

Large large = new Large
{
    Id = smalls[0].Id,
    Aa = smalls[0].Value,
    Bb = smalls[1].Value,
    Cc = smalls[2].Value,
    Dd = smalls[3].Value,
    Ee = smalls[4].Value,
    Ff = smalls[5].Value,
    Gg = smalls[6].Value
}

      

So I try to rule out the assumption that they are in the correct order and populate new fields based on the Name string in the Small object into the corresponding field in the Large object.

Thanks for any input!

+3


source to share


5 answers


So I try to rule out the assumption that they are in the correct order.

Is something like this possible ?:

Aa = smalls.Single(s => s.Name == "Aa").Value

      

This at least still relies on the assumption that the entry will be there at all, although it doesn't care about the order of the entries. If you want to drop this assumption, you can add some error checking. Something like this is possible:



Aa = smalls.Any(s => s.Name == "Aa") ? smalls.First(s => s.Name == "Aa") : string.Empty

      

It is not the most efficient thing in the world, but at least stays on the same line as in your current use. Splitting across multiple lines will make it longer, but potentially more efficient (if performance is even an issue ... in a very small example, if it really isn't).

Perhaps these multiple lines can be re-accounted for in a custom extension method to return it on a single line? The sky is the limit, indeed.

+1


source


You can group values ​​using Id

and create multiple methods to retrieve values ​​based on a field Name

:

private static string GetValueByName(IDictionary<string,string> data, string name) {
    string res;
    return data.TryGetValue(name, out res) ? res : null;
}
private static Large MakeFromAttributes(IEnumerable<Small> data, int id) {
    var byName = data.ToDoctionary(s => s.Name, s => s.Value);
    return new Large {
        Id = id
    ,   Aa = GetValueByName(byName, "Aa")
    ,   Bb = GetValueByName(byName, "Bb")
    ,   Cc = GetValueByName(byName, "Cc")
    ,   Dd = GetValueByName(byName, "Dd")
    ,   Ee = GetValueByName(byName, "Ee")
    ,   Ff = GetValueByName(byName, "Ff")
    ,   Gg = GetValueByName(byName, "Gg")
    };
}

      



With these helper methods, you can build a LINQ query like this:

var largeList = smalls
    .GroupBy(s => s.Id)
    .Select(g => MakeFromAttributes(g, g.Key))
    .ToList();

      

+2


source


A Dictionary(TKey, TValue)

might be a more appropriate data structure for a use case, since keys can be dynamic, and any decision going from IEnumerable(Small)

to Large

will have to make assumptions about the composition of the collection or collection of objects.

A simple dynamic solution is to use reflection, but this will have more overhead compared to the static search already suggested.

public static Large CreateLargeFromSmalls(int id, IEnumerable<Small> smalls)
{
    var largeType = typeof(Large);
    var large = new Large { Id = id };

    foreach (var small in smalls)
    {
        var prop = largeType.GetProperty(small.Name);
        if (prop != null)
        {
            prop.SetValue(large, small.Value);
        }
    }

    return large;
}

      

Assumptions

  • Name

    Small

    will exactly match the corresponding property Large

    .
  • Name

    of is Small

    not unique, and the order of the iterations matters.
  • If the property corresponding to Name

    of Small

    does not exist in Large

    , then it is not displayed.

Arguments

  • Changes to the contract before Large

    do not affect the display logic.

against

  • Reflection overhead

Example:

var smalls = new List<Small>
{
    new Small{Name = "Aa", Id = 1, Value = "v1"},
    new Small{Name = "Bb", Id = 1, Value = "v2"},
    new Small{Name = "Cc", Id = 1, Value = "v3"},
    new Small{Name = "Dd", Id = 1, Value = "v4"},
    new Small{Name = "Ee", Id = 1, Value = "v5"},
    new Small{Name = "Ff", Id = 1, Value = "v6"},
    new Small{Name = "Gg", Id = 1, Value = "v7"}
};

var bigs =
    smalls
        .GroupBy(x => x.Id)
        .Select(g => CreateLargeFromSmalls(g.Key, g))
        .ToList();

      

+1


source


If I understand your question correctly, you can use reflection to set all properties in a class Large

without worrying about the order of the collection Small

:

Large large = new Large();
foreach (var propertyInfo in large.GetType().GetProperties())
{
    var sm = smalls.FirstOrDefault(small => string.Equals(small.Name, propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase));
    if (sm != null)
        propertyInfo.SetValue(large, Convert.ChangeType(sm.Value, propertyInfo.PropertyType), null);
}

      

Important. Note that for this solution, all properties Large

that need to be updated MUST be tagged with getters and setters. eg.public string Aa { get; set; }

First we get all the properties Large

using large.GetType().GetProperties()

which gets all the properties. We then compare the names with the property .Name

in the collection of classes Small

, and if we find a match, we will set the property's value. You can read more about reflection here .

Screenshot Large

after verification:

qhjKP.png

0


source


You can group your list Small

and then set Id

as Key

group and other properties using reflection, it will look like this:

var large = smalls.GroupBy(small => small.Id)
    .Select(group =>
    {
        var result = new Large();
        result.Id = group.Key;
        var largeType = result.GetType();
        foreach (var small in group)
        {
                largeType.GetProperty(small.Name).SetValue(result, small.Value);
        }
        return result;
    }).First();

      

0


source







All Articles