Since my application has to work with a lot of identifiers, I thought about abstracting them using a Id<T>
class. This class would work like a Guid, it has a CreateNew()
method and so on, but would create a different Id depending on the type of T.
Examples:
var id = Id<Individual>.CreateNew(); // == "INDIVIDUAL-12345"
var id = Id<Organization>.CreateNew(); // == "ORGANIZATION-67890"
Now, Individual
and Organization
implement the interface IUser
.
Now I would like my Id<> to handle different implementations, but this does not work.
ie consider the following class, using Ids:
class Transaction{
private Id<IUser> _sender;
private Id<IUser> _receiver;
private Amount _amount;
public Transaction(Id<IUser> sender, Id<IUser> receiver, Amount amount {...}
}
I would like to be able to call:
new Transaction(Id<Individual>.CreateNew(), Id<Organization>.CreateNew(), new Amount());
but this is not possible as the constructor requires the types to be Id<IUser>
. Can you think about any workaround?
Update
Thanks for the answers until now.
Id<IUser>
is just an example (and the content of IUser is not that important here), I'd like to support identifiers for many objects (10/15, more in the future?).
Id<T>
uses a static class as helper, called TypesMatcher, whose job is to translate a type to a prefix (ie "Individual", "Organization", "Transaction"..).
Id<T>.CreateNew()
looks more or less like this:
[Pure]
public static Id<T> CreateNew()
{
if (!TypesMatcher.IsSupported<T>()) throw new Exception($"Type {typeof(T)} is not supported.");
string prefix = TypesMatcher.GetPrefixForType<T>();
string body = Guid.NewGuid().ToString();
return new Id<T>($"{prefix}-{body}");
}
I'd start by looking into your desired syntax: Id<Individual>.CreateNew()
.
It means there should be an Id<T>
class and have a static CreateNew
method.
The static
method cannot be overridable, so a good way to make it extensible, is injecting the behavior inside the method.
So for each entity type we can create a class like SomeEntityId
deriving from Id<SomeEntity>
. The class should contain the custom logic for generating the id. Then we can register them in a DI container. Then in the static CreateNew
method, I'd get instance of the registered classes based on T
from the container. This way you can inject the custom logic based on T
.
I'll also change the Transaction
class to rely on IId<IUser>
, this way I can create a Variant Generic Interface like IId<T>
.
Example
public interface IId<out T> where T : IUser
{
string Value { get; }
}
public class Id<T> : IId<T> where T : IUser
{
public string Value { get; protected set; }
protected virtual Id<T> GenerateId() => new Id<T>() { Value = $"{Guid.NewGuid()}" };
public static Id<T> CreateNew() => Container.GetInstance<Id<T>>().GenerateId();
}
public class IndividualId : Id<Individual>
{
protected override Id<Individual> GenerateId()
{
var instance = new IndividualId();
instance.Value = $"IndividualId-{Guid.NewGuid()}"; //Example of custom logic
return instance;
}
}
public class Transaction
{
private IId<IUser> _sender;
private IId<IUser> _receiver;
public Transaction(IId<IUser> sender, IId<IUser> receiver) { }
}
For each type which you have custom logic, make sure you register the type instance for Id<yourtype>
, for example register IndividualId
for Id<Individual>
in the container.
Then for usage, you can use this:
new Transaction(Id<Individual>.CreateNew(), Id<Organization>.CreateNew());
I am not sure I am getting your goal/the problem you are trying to solve here.
Generics are about allowing a person that is using your code to put in any class, without breaking compile time type checks . Prior to generics, we had to either make a special collection for every type or use Object as the type. One was a unsuiteable amount of work with tons of pitfalls. The other just broke type safety. It is just a mess we mostly prefer to stay burried in pre-generics history. Still I feel it is important to understand why Generics exist, what they are about solving and what they can not solve.
As you use a Interface function to access the values, they can only be resolved at runtime. While they might be a compile time constant in the class, there is no way for the compiler to ever be sure of it as they are handed out by a function at runtime. However it seems possible to add a sort-of-compile time Constant to Interfaces, with the Literal Keyword . But I am unsure if Implementers of that interface can add a tiffrent "typeID" value. I just read about this being a thing, while writing.
Personally that code you shared reminds me a lot of a variant of the Factory/not quite singleton pattern. A factory method where the same input (the same Collection, Type) will result in the same output. But I can not find a example right now.
With the shared code, the only thing I can see being made generic is the transaction Method:
public <senderType, ReceiverType> Transaction (senderType sender, ReceiverType receiver, Amount amount) { }
But again, I am unsure what you tried to do and particular the problem you tried to solve.
Generics cannot handle any idea of inheritance. Covariance is only available on Interfaces.
If you have a covariant interface
interface IID<out IUser>
{
IID<IUser> CreateNew();
}
And you use this instead of ID<User>
, and Individual and Organization implement IUser
, and ID<Invdidual>
implements IID<Individual>
and ID<Organization>
implements IID<Organization>
.
Then you can make this assigment:
IID<IUser> id = = ID<Individual>.CreateNew();
or use it as a Parameter.
But on the other hand side, to use it in this way:
IID<Individual> id = (IID<Individual>)ID<Individual>.CreateNew();
you would need a cast.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.