C
C#2d ago
Victor H

Generic static factories

Hello. I have the following interface:
public interface IStronglyTypedId<TValue, out TSelf>
where TSelf : IStronglyTypedId<TValue, TSelf>
{
TValue Value { get; }
static abstract TSelf From(TValue value);
}
public interface IStronglyTypedId<TValue, out TSelf>
where TSelf : IStronglyTypedId<TValue, TSelf>
{
TValue Value { get; }
static abstract TSelf From(TValue value);
}
This works well but I want to have a specific type of strongly typed id's of type AggregateId which must have an underlying backing type of Guid, especially a version 7 guid. So basically how can I have default implementations for abstract records? I tried this but infinite recursion.
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TSelf>
{
public static TSelf From(Guid value) => TSelf.From(value); // <- infinite recursion
}
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TSelf>
{
public static TSelf From(Guid value) => TSelf.From(value); // <- infinite recursion
}
10 Replies
太卜『符玄』
Hello! The problem you have is that in the abstract record AggregateId<TId>, the implementation of the From method calls itself, resulting in infinite recursion. Specifically, TSelf.From(value) actually calls the From method of the current class instead of the concrete subclass implementation, which results in recursive calls and eventually throws a stack overflow.
Victor H
Victor HOP2d ago
Yes
太卜『符玄』
so avoid to invoke From, you can:
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TId>
{
public static TId From(Guid value) => Create(value);

protected abstract static TId Create(Guid value);
}
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TId>
{
public static TId From(Guid value) => Create(value);

protected abstract static TId Create(Guid value);
}
public record MyAggregateId(Guid Value) : AggregateId<MyAggregateId>(Value)
{
protected override static MyAggregateId Create(Guid value) => new MyAggregateId(value);
}
public record MyAggregateId(Guid Value) : AggregateId<MyAggregateId>(Value)
{
protected override static MyAggregateId Create(Guid value) => new MyAggregateId(value);
}
or , if you want use reflection.
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TId>
{
public static TId From(Guid value)
{
var constructor = typeof(TId).GetConstructors()
.FirstOrDefault(c => c.GetParameters().Length == 1 && c.GetParameters()[0].ParameterType == typeof(Guid));
if (constructor == null)
{
throw new InvalidOperationException($"No suitable constructor found for type {typeof(TId).Name}");
}

return (TId)constructor.Invoke(new object[] { value });
}
}
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TId>
{
public static TId From(Guid value)
{
var constructor = typeof(TId).GetConstructors()
.FirstOrDefault(c => c.GetParameters().Length == 1 && c.GetParameters()[0].ParameterType == typeof(Guid));
if (constructor == null)
{
throw new InvalidOperationException($"No suitable constructor found for type {typeof(TId).Name}");
}

return (TId)constructor.Invoke(new object[] { value });
}
}
Victor H
Victor HOP2d ago
An overridable method cannot be static so this will not work.
太卜『符玄』
oh, I see. sorry if it must be static, you can derived it.
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TId>
{
public static TId From(Guid value)
{
return Activator.CreateInstance<TId>(value);
}
}

public record ConcreteAggregateId(Guid Value) : AggregateId<ConcreteAggregateId>(Value)
{
public static new ConcreteAggregateId From(Guid value) => new ConcreteAggregateId(value);
}
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TId>
{
public static TId From(Guid value)
{
return Activator.CreateInstance<TId>(value);
}
}

public record ConcreteAggregateId(Guid Value) : AggregateId<ConcreteAggregateId>(Value)
{
public static new ConcreteAggregateId From(Guid value) => new ConcreteAggregateId(value);
}
Victor H
Victor HOP2d ago
Yeah I figured you could use reflection, but wondering if it is possible without really.
太卜『符玄』
avoid reflection, you can use factory method.
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TId>
{
private static Func<Guid, TId> _factory;

public static TId From(Guid value) => _factory(value);

protected static void SetFactory(Func<Guid, TId> factory)
{
_factory = factory;
}
}
public abstract record AggregateId<TId>(Guid Value) : IStronglyTypedId<Guid, TId>
where TId : IStronglyTypedId<Guid, TId>
{
private static Func<Guid, TId> _factory;

public static TId From(Guid value) => _factory(value);

protected static void SetFactory(Func<Guid, TId> factory)
{
_factory = factory;
}
}
derived it:
public record ConcreteAggregateId(Guid Value) : AggregateId<ConcreteAggregateId>(Value)
{
static ConcreteAggregateId()
{
SetFactory(value => new ConcreteAggregateId(value));
}
}
public record ConcreteAggregateId(Guid Value) : AggregateId<ConcreteAggregateId>(Value)
{
static ConcreteAggregateId()
{
SetFactory(value => new ConcreteAggregateId(value));
}
}
Victor H
Victor HOP2d ago
Thanks for your help but I'm not really a fan of either of these but I appreciate it ❤️
太卜『符玄』
QwQ,You're welcome
Anton
Anton11h ago
What do you need this to be an interface for?

Did you find this page helpful?