1

I've been working on a library to generate fake data using Faker.NET. The problem I'm having is that I don't know how to access an anonymous method that I'm passing to the constructor of my DataGenerator child classes.

The issue is that in order to create a list of generics I had to create base class DataGenerator but I cannot pull my Func<T> member up because that base class is not generic so no Tavailable. However, my DataGenerator<T> class does expose the Generator property which is my anonymous method but I haven't found a way to access it while iterating my list of data generators.

Any advice will be highly appreciated.

This is what I have so far:

public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Guid EmpUid { get; set; }
}

// Define other methods and classes here
public abstract class DataGenerator
{
    public abstract int GetWeight(string matchingProperty);
    public abstract Type Type { get;}
}

public abstract class DataGenerator<T> : DataGenerator
{
    public readonly string[] Tags;
    public readonly Func<T> Generator; 
    protected DataGenerator(Func<T> generator, params string[] tags)
    {
        Tags = tags;
        //How to access this?
        Generator = generator;
    }

    public override int GetWeight(string matchingProperty)
    {
        int sum = (from tag in Tags
            where matchingProperty.ToLowerInvariant().Contains(tag.ToLowerInvariant())
            select 1).Sum();
        return sum;
    }

    public override Type Type {
        get { return typeof(T); }
    }
}

public class StringDataGenerator : DataGenerator<string>
{
    public StringDataGenerator(Func<string> generator, params string[] tags) : base(generator, tags)
    {
    }
}

public class GuidDataGenerator : DataGenerator<Guid>
{
    public GuidDataGenerator(Func<Guid> generator, params string[] tags)
        : base(generator, tags)
    {
    }
}

And I'm testing it here:

private static void Main(string[] args)
    {
        var dataGeneratorList = new List<DataGenerator>
        {
            new StringDataGenerator(Name.First, "first", "name"),
            new StringDataGenerator(Name.Last, "last", "name"),
            new GuidDataGenerator(Guid.NewGuid, "uid", "id")
        };

        var writeProperties = typeof (Employee).GetProperties().Where(p => p.CanWrite);
        foreach (var property in writeProperties)
        {
            foreach (var dataGenerator in dataGeneratorList)
            {
                if (property.PropertyType == dataGenerator.Type)
                {
                    var weigth = dataGenerator.GetWeight(property.Name);
                    //How to access generator here???
                    var testValue = dataGenerator.Generator.Invoke();
                }
            }
        }
    }
Adolfo Perez
  • 2,834
  • 4
  • 41
  • 61
  • 2
    why not add an overload with `object` as abstract in your base class and implement it with a cast to `T`? –  Apr 28 '15 at 22:34
  • I think i tried that earlier @AndreasNiedermair but for some reason I couldn't get it to work – Adolfo Perez Apr 28 '15 at 22:43
  • The reason adding an abstract `object` overload in the base class doesn't work is that in my case Guid.NewGuid returns a Guid which is not an object but a `struct`, which doesn't inherit from `System.Object` – Adolfo Perez Apr 28 '15 at 22:51
  • but there's boxing to your rescue... –  Apr 30 '15 at 17:21

1 Answers1

1

As you tagged, given your current setup, reflection is probably your only option.

var func = dataGenerator.GetType().GetField("Generator").GetValue(dataGenerator);
var testValue = func.GetType().GetMethod("Invoke").Invoke(func, null);

I'm not sure anyone could call this super nice, and it won't be super fast, but it's probably sufficient for anything you need fake data in, I suppose.

For good measure, here's it in action.


Your question is actually a bit more complicated than it may seem at face-value. A nice way of handling this if you only ever use it in object form is just to add an abstract Generate method to the base, non-generic class:

public abstract object Generate();

Then override it in your generic one:

public override object Generate()
{
    return this.Generator();
}

Of course, this return an object, which isn't nice in a generic class. But at least it avoids reflection.

Another solution to avoid this reflection nonsense might be the use of covariance, although that will, unfortunately, break for value types, like Guid.

public interface IDataGenerator<out T>
{
    int GetWeight(string matchingProperty);
    Type Type { get;}
    T Generate();
}

public abstract class DataGenerator<T> : IDataGenerator<T>
{
    public readonly string[] Tags;
    public readonly Func<T> Generator; 
    protected DataGenerator(Func<T> generator, params string[] tags)
    {
        Tags = tags;
        //How to access this?
        Generator = generator;
    }

    public T Generate(){
        return this.Generator();
    }

    . . .
}

That then turns into a preferable,

private static void Main(string[] args)
{
    var dataGeneratorList = new List<IDataGenerator<object>>
    {
        new StringDataGenerator(Name.First, "first", "name"),
        new StringDataGenerator(Name.Last, "last", "name")

//            But this line doesn't work
//            new GuidDataGenerator(Guid.NewGuid, "uid", "id")
    };

    var writeProperties = typeof (Employee).GetProperties().Where(p => p.CanWrite);
    foreach (var property in writeProperties)
    {
        foreach (var dataGenerator in dataGeneratorList)
        {
            if (property.PropertyType == dataGenerator.Type)
            {
                var weigth = dataGenerator.GetWeight(property.Name);

                var testValue = dataGenerator.Generate();
            }
        }
    }
}
Community
  • 1
  • 1
Matthew Haugen
  • 12,916
  • 5
  • 38
  • 54
  • Thanks @MathewHaugen. Interesting solutions. I think you also came to the conclusion that Guids are throwing me off from coming up with an elegant solution. So far the reflection trick seems to be doing the job. I'll wait so see if there are more suggestions. – Adolfo Perez Apr 28 '15 at 22:57
  • 1
    @AdolfoPerez Guids are only an issue when using covariance. They work fine for the second approach, with a method that returns `object`. My only issue with that comes in principle, and that it feels dirty returning an `object` when I actually know what type it'll be (`T`). But it works. – Matthew Haugen Apr 28 '15 at 22:58
  • I think your second option is the best for what I need. Marked as answered. Thanks for your help! – Adolfo Perez Apr 29 '15 at 00:14