Home > Software engineering >  Any syntax sugar for instancinating an object with properties coming from IEnumerable<T>
Any syntax sugar for instancinating an object with properties coming from IEnumerable<T>

Time:01-05

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.");
    }
}
  •  Tags:  
  • c#
  • Related