Mahmoud Samy
Mahmoud Samy

Reputation: 2852

Creating an IEnumerable<> from a generator method using a factory function

I have a factory function, and want to use it to create an Enumerable. In C# I didn't find a straight-forward declarative way to do the job. So I did it like this:

public IEnumerable<Contact> Get()
{
    return Enumerable.Range(1, 5).Select(x => GenerateRandomContact());
}

Is there any better way?

// Expected format
public IEnumerable<Contact> Get()
{
    return Enumerable.Generate(GenerateRandomContact).Take(5);
}

Upvotes: 6

Views: 5154

Answers (2)

Zinov
Zinov

Reputation: 4119

public IEnumerable<Contact> Get()
{
    for(int i = 1; i <= 5; i ++)
    {
       yield return GenerateRandomContact();
    }
}

or if you want a specific amount you can do this

public IEnumerable<Contact> Get(int number)
{
    for(var i = 1; i <= number; i ++)  //is the same as for (var i = 1;; i++) you got the idea
    {
        yield return GenerateRandomContact();
    }
}

You are expecting something like Enumerable.Generate()

In order to get a list of elements that you want to Enumerate, your Generate method should return an Enumerable Value. You are trying to create an extension method for Enumerable based on your approach. Before that try to see the implementation of Range

public static IEnumerable<int> Range(int start, int count) 
{
     long max = ((long)start) + count - 1;
     if (count < 0 || max > Int32.MaxValue) throw Error.ArgumentOutOfRange("count");
            return RangeIterator(start, count);
}

static IEnumerable<int> RangeIterator(int start, int count) 
{
    for (int i = 0; i < count; i++) yield return start + i;
}

In that case with the yield, .NET creates the IEnumerable class for you and based on a specific amount. Maybe you should consider passing the amount on the Generate method, or like the other extensions as Concat, you should pass the IEnumerable, see bellow how they pass to the Contact mehotd the IEnumerable first variable

public static IEnumerable<TSource> Concat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second) 
{
    if (first == null) throw Error.ArgumentNull("first");
    if (second == null) throw Error.ArgumentNull("second");
    return ConcatIterator<TSource>(first, second);
}

With that said.

You can use my first approach or your GenerateRandomContact should return an IEnumerable of Contact, but there is the problem, how much elements you want on that Random creation, or you don't specify the amount as bellow

public IEnumerable<Contact> GenerateRandomContact()
 {

    var random = new Random(Environment.TickCount);
    for (int i = 0; i < 100; i++)
    {
        yield return new Contact
        {
            Name = $"Name_{random.Next()}"
        };
    }
 }

or you pass the parameter

But the whole problem now is, if you want to call your method as Enumerable.Generate, that is not possible, You can't have extension methods on static classes because extension methods are only applicable to instantiable types and static classes cannot be instantiated.

Your call should be

IEnumerable<Contact> contacts = new List<Contact>(){....}
//where contacts is an instance of type
contacts.Generate 

even in the case you want this syntax

 contacts.Generate(GenerateRandomContact).Take(5);

your extension method should accept a function but here I am relying on the previous GenerateRandomContact() where I put 100 random contacts

public static class Extension
{
    public static IEnumerable<T> Generate<T>(this IEnumerable<T> elements, Func<IEnumerable<T>> func)
    {
        if (func != null)
        {
            return func();
        }

        return Enumerable.Empty<T>();
    }
}

IMHO try to consider passing the amount that you want to take. Your syntax will change a little bit

class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<Contact> contacts = new List<Contact>()
            {
                new Contact
                {
                    Name = "Name1"
                },
                new Contact
                {
                    Name = "Name2"
                }
            }; //load your methods from the database or create them here

            var res = contacts.Generate(GenerateRandomContact, 5);

            Console.ReadLine();
        }

        static IEnumerable<Contact> GenerateRandomContact(int amount)
        {
            var random = new Random(Environment.TickCount);

            for (int i = 0; i < amount; i++)
            {
                yield return new Contact
                {
                    Name = $"Name_{random.Next()}"
                };
            }
        }

    }

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

    public static class Extension
    {
        public static IEnumerable<T> Generate<T>(this IEnumerable<T> elements, Func<int, IEnumerable<T>> func, int amount)
        {
            if (func != null)
            {
                return func(amount);
            }

            return Enumerable.Empty<T>();
        }
    }
}

Upvotes: 1

Jeroen van Langen
Jeroen van Langen

Reputation: 22038

Something like:

public static IEnumerable<T> Generate(Func<T> generator)
{
    while(true)
        yield return generator.Invoke();
}

But you can't extend static classes. So you should place it in a helper class.

Upvotes: 9

Related Questions