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!
source to share
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.
source to share
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();
source to share
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 propertyLarge
. -
Name
of isSmall
not unique, and the order of the iterations matters. - If the property corresponding to
Name
ofSmall
does not exist inLarge
, 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();
source to share
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:
source to share
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();
source to share