5

I have the following two classes:

public class KeyedEntity<TEntity>
{
    internal KeyedEntity() { }

    public Identifier Key { get; set; }
    public TEntity Entity { get; set; }
}

public static class KeyedEntity
{
    public static KeyedEntity<TEntity> Create<TEntity>(Identifier key, TEntity entity)
    {
        return new KeyedEntity<TEntity>
        {
            Key = key,
            Entity = entity,
        };
    }
}

The reason the constructor is internal and the second class exists is I want to enforce the more highly-maintainable KeyedEntity.Create(x, y) syntax rather than new KeyedEntity<T>{ Key = x, Entity = y }. (Note that the type is inferred with the former syntax.)

I want to tell AutoFixture how to create an instance of KeyedEntity. However, the Register method only seems to allow registration of a single type rather than an open generic type.

How can I register KeyedEntity.Create<TEntity> as the creation function for KeyedEntity<TEntity>?

Sam
  • 40,644
  • 36
  • 176
  • 219
  • 1
    Why do you consider `KeyedEntity.Create(x, y)` more maintainable than `new KeyedEntity(x, y)`? – Mark Seemann Nov 26 '13 at 07:59
  • @Mark: A factory method may return a (future) subclass, a constructor will never. – eFloh Nov 26 '13 at 08:12
  • @eFloh Ah, I actually hadn't considered that, since I never design my code around subclassing (*favor composition over inheritance*) :) – Mark Seemann Nov 26 '13 at 08:18
  • 1
    @MarkSeemann, because C# supports type inference for methods, whereas it doesn't for constructors. – Sam Nov 26 '13 at 08:39
  • @Sam It saves you a few keystrokes, but how does it make your code more *maintainable*? – Mark Seemann Nov 26 '13 at 10:35
  • related: http://stackoverflow.com/questions/10092446/autofixture-configuring-an-open-generics-specimen-builder – Ruben Bartelink Nov 26 '13 at 13:06
  • @MarkSeemann, it comes down to the DRY principle; if I change something, I want to minimise the number of places in which I have to change it. In this case, if I change the type of something that I add to a new `KeyedEntity`, with type inference, I don't need to change the call to `KeyedEntity`. – Sam Nov 26 '13 at 20:17
  • 2
    @Sam If you have calls to `new KeyedEntity(x, y)` all over the place, you have a tightly coupled system. Creating a static, concrete factory doesn't solve *that* problem. It sounds to me like you're attempting to address a symptom instead of a root problem. – Mark Seemann Nov 26 '13 at 20:32
  • @MarkSeemann, I don't have calls to it all over the place. However, I do have multiple calls to it in the application's data access layer. I think it's inevitable that use of a DTO produces tight coupling to the DTO. Are you suggesting I use an interface for the DTO? I'm keen to hear what you think the underlying problem is; I don't usually get useful feedback on my work. – Sam Nov 27 '13 at 00:06
  • @Sam IME, making a constructor internal and exposing a concrete factory method rarely provides much benefit. Now your code is more complicated than before, and I don't believe that you've gained much. – Mark Seemann Nov 27 '13 at 08:08
  • @MarkSeemann, thanks. I'll consider that. – Sam Nov 27 '13 at 22:10

2 Answers2

4

To support your open generic type, you can write a custom Specimen Builder:

public class KeyedEntityBuilder : ISpecimenBuilder
{
    private readonly static MethodInfo createMethod =
        typeof(KeyedEntity).GetMethod("Create");

    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;
        if (t == null ||
            !t.IsGenericType ||
            t.GetGenericTypeDefinition() != typeof(KeyedEntity<>))
            return new NoSpecimen(request);

        var entityType = t.GetGenericArguments().Single();

        var key = context.Resolve(typeof(Identifier));
        var entity = context.Resolve(entityType);

        return createMethod
            .MakeGenericMethod(entityType)
            .Invoke(null, new[] { key, entity });
    }
}

(Defensive coding omitted for clarity.)

The following unit test passes:

public class Tests
{
    [Fact]
    public void CreateKeyedEntity()
    {
        var fixture = new Fixture();
        fixture.ResidueCollectors.Add(new KeyedEntityBuilder());

        var actual = fixture.Create<KeyedEntity<Foo>>();

        Assert.NotNull(actual.Key);
        Assert.NotNull(actual.Entity);
    }
}

For better maintainability, you should encapsulate KeyedEntityBuilder in a Customization.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
0

Assuming that you have a set of derived types, e.g.:

public class A: KeyedEntity<A>
{
}

public class B: KeyedEntity<B>
{
}

Since the above object graph contains a circular reference (on T) you need to configure the Fixture instance to omit assignments on first recursion:

Then you Create a generic method that will customize the creation algorithm for KeyedEntity<T>:

internal void CustomizeKeyedEntity<T>(IFixture fixture)
{
    fixture.Customize<KeyedEntity<T>>(c =>
        c.FromFactory(() =>
            KeyedEntity.Create(
                fixture.Create<Identifier>(),
                fixture.Create<T>())));
}

You may use the above method as:

this.CustomizeKeyedEntity<A>(fixture);
this.CustomizeKeyedEntity<B>(fixture);

Example

[Fact]
public void Test()
{
    var fixture = new Fixture();

    this.CustomizeKeyedEntity<A>(fixture);
    this.CustomizeKeyedEntity<B>(fixture);

    var actualA = fixture.Create<A>();
    var actualB = fixture.Create<B>();
}
Nikos Baxevanis
  • 10,868
  • 2
  • 46
  • 80
  • Are you sure about that circular reference? – Mark Seemann Nov 26 '13 at 08:00
  • This still looks like it has the same problem as just using the `Register` method; it doesn't allow for open-generic registration. I know I could still just make a registration for each constructed/closed generic type I want to use, but that isn't as maintainable as I would like. I might see if I can improve the question so it's clear that it's about registering an open generic type and factory. – Sam Nov 26 '13 at 08:47
  • @Sam There is not much you can do (yet) with open generics (see [this thread](https://autofixture.codeplex.com/workitem/3387)). However, as [Mark Seemann wrote](http://stackoverflow.com/questions/20209469/how-can-i-register-a-generic-object-factory/20211376#comment30137336_20209469) you may consider `new KeyedEntity(x, y)`. – Nikos Baxevanis Nov 26 '13 at 08:54
  • I see... it was the result of the `CustomizeKeyedEntity` (notice the last `fixture.Create()`. Thank you - I will update the answer. – Nikos Baxevanis Nov 26 '13 at 10:14