I'm instantiating an object with properties coming from a IEnumerable. The straightforward way is
IEnumerable<string> source = ...
var instance = new X(
source.First(),
source.Skip(1).First(),
source.Skip(2).First(),
...
);
Where X
is a class with a constructor that takes a number of parameters.
This works, but it feels like this is a common scenario (for example fetching data using a generic storage layer and instantiating a specific record type) that there should be a cleaner way.
I considered making a list using a .ToList()
and then access the properties using the indexer, but that evaluates the whole Enumerable which I don't feel is warranted here.
Is there a cleaner approach? I was imagining an Enumerator approach that would allow me to .MoveNext()
& .Current
approach, that would allow me O(1) access and no unnecessary allocations -- but with some syntax sugar to make that pretty
CodePudding user response:
It looks like you're trying to take the first N values from the series, for some value of N.
If the X(...)
takes a params string[]
, then you can just use source.Take(N).ToArray()
; since we'll be building the array anyway, this has no particular additional overhead.
If the X(...)
takes N
separate string
parameters, then you do need to unroll it, but iterating the sequence multiple times is awkward. It may be ugly, but I'd probably use something custom here:
string a, b, c;
using (IEnumerator<string> iter = source.GetEnumerator())
{
a = iter.Next();
b = iter.Next();
c = iter.Next();
}
return new X(a, b, c);
static class Utils
{
public static T Next<T>(this IEnumerator<T> source)
{
if (!source.MoveNext()) Throw();
return source.Current;
static void Throw() => throw new InvalidOperationException("Missing element from sequence");
}
}
You could also move the constructor inside the using
, if you don't mind extending the sequence living a little longer (into the constructor invoke):
using IEnumerator<string> iter = source.GetEnumerator();
return new X(iter.Next(), iter.Next(), iter.Next());
CodePudding user response:
You can pass parameters via activator.createinstanc<T>
See here: https://learn.microsoft.com/en-us/dotnet/api/system.activator.createinstance?redirectedfrom=MSDN&view=net-7.0#System_Activator_CreateInstance_System_Type_System_Object___
Try this:
using System;
using System.Collections.Generic;
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public Person(string firstName, string lastName, int age)
{
FirstName = firstName;
LastName = lastName;
Age = age;
}
}
class Program
{
static void Main(string[] args)
{
List<object> constructorArgs = new List<object>() { "David", "Božjak", 30 };
Person p = (Person)Activator.CreateInstance(typeof(Person), constructorArgs.ToArray());
Console.WriteLine($"{p.FirstName} {p.LastName} is {p.Age} years old.");
}
}