Is there a way to force the compiler to restrict the usage of a custom attribute to be used only on specific property types like int, short, string (all the primitive types)?
similar to the AttributeUsageAttribute's ValidOn-AttributeTargets enumeration.
No, you can't, basically. You can limit it to struct vs class vs interface, that is about it. Plus: you can't add attributes to types outside your code anyway (except for via TypeDescriptor, which isn't the same).
You can run this unit test to check it.
First, declare validation attribute PropertyType:
[AttributeUsage(AttributeTargets.Class)]
// [JetBrains.Annotations.BaseTypeRequired(typeof(Attribute))] uncomment if you use JetBrains.Annotations
public class PropertyTypeAttribute : Attribute
{
public Type[] Types { get; private set; }
public PropertyTypeAttribute(params Type[] types)
{
Types = types;
}
}
Create unit test:
[TestClass]
public class TestPropertyType
{
public static Type GetNullableUnderlying(Type nullableType)
{
return Nullable.GetUnderlyingType(nullableType) ?? nullableType;
}
[TestMethod]
public void Test_PropertyType()
{
var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes());
var allPropertyInfos = allTypes.SelectMany(a => a.GetProperties()).ToArray();
foreach (var propertyInfo in allPropertyInfos)
{
var propertyType = GetNullableUnderlying(propertyInfo.PropertyType);
foreach (var attribute in propertyInfo.GetCustomAttributes(true))
{
var attributes = attribute.GetType().GetCustomAttributes(true).OfType<PropertyTypeAttribute>();
foreach (var propertyTypeAttr in attributes)
if (!propertyTypeAttr.Types.Contains(propertyType))
throw new Exception(string.Format(
"Property '{0}.{1}' has invalid type: '{2}'. Allowed types for attribute '{3}': {4}",
propertyInfo.DeclaringType,
propertyInfo.Name,
propertyInfo.PropertyType,
attribute.GetType(),
string.Join(",", propertyTypeAttr.Types.Select(x => "'" + x.ToString() + "'"))));
}
}
}
}
Your attribute, for example allow only decimal property types:
[AttributeUsage(AttributeTargets.Property)]
[PropertyType(typeof(decimal))]
public class PriceAttribute : Attribute
{
}
Example model:
public class TestModel
{
[Price]
public decimal Price1 { get; set; } // ok
[Price]
public double Price2 { get; set; } // error
}
You could write code yourself to enforce correct use of your attribute class, but that's as much as you can do.
The code below will return an error if the attribute was placed on a property/field that is not List of string.
The line if (!(value is List<string> list)) may be a C#6 or 7 feature.
[AttributeUsage(AttributeTargets.Property |
AttributeTargets.Field, AllowMultiple = false)]
public sealed class RequiredStringListAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (!(value is List<string> list))
return new ValidationResult($"The required attrribute must be of type List<string>");
bool valid = false;
foreach (var item in list)
{
if (!string.IsNullOrWhiteSpace(item))
valid = true;
}
return valid
? ValidationResult.Success
: new ValidationResult($"This field is required"); ;
}
}
The way I am doing this is following:
[AttributeUsage(AttributeTargets.Property)]
public class SomeValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value is not string stringToValidate)
{
throw new AttributeValueIsNotStringException(validationContext.DisplayName, validationContext.ObjectType.Name);
}
// validationContext.DisplayName is name of property, where validation attribut was used.
// validationContext.ObjectType.Name is name of class, in which the property is placed to instantly identify, where is the error.
//Some validation here.
return ValidationResult.Success;
}
}
And exception look like this:
public class AttributeValueIsNotStringException : Exception
{
public AttributeValueIsNotStringException(string propertyName, string className) : base(CreateMessage(propertyName, className))
{
}
private static string CreateMessage(string propertyName, string className)
{
return $"Validation attribute cannot be used for property: \"{propertyName}\" in class: \"{className}\" because it's type is not string. Use it only for string properties.";
}
}
Related
I am creating a project that will manage app configurations. It will be very generic, reusable across different apps (with different config models on each) and very flexible - including the ability to create/save/store/read/merge partial configurations from multiple sources.
Without getting too much into details, here's an example of what I need to do.
I have a class like below:
public class TestConfigModel
{
public int SomeIntValue { get; set; }
public string SomeStringValue { get; set; }
public TestConfigSubsection Subsection { get; set; }
}
public class TestConfigSubsection
{
public System.DayOfWeek SomeSubsectionEnumValue { get; set; }
public Guid SomeSubsectionGuidValue { get; set; }
}
I need to dynamically generate a version of this model that has all properties nullable (unless they already take a null):
public class TestConfigModelNullable
{
public int? SomeIntValue { get; set; }
public string SomeStringValue { get; set; } // already takes a null
public TestConfigSubsection Subsection { get; set; } // already takes a null
}
public class TestConfigSubsectionNullable
{
public System.DayOfWeek? SomeSubsectionEnumValue { get; set; }
public Guid? SomeSubsectionGuidValue { get; set; }
}
Example use:
I have a default (complete) config like so:
var aConfigInstance = new TestConfigModel()
{
SomeIntValue = 3,
SomeStringValue = "hey",
Subsection = new TestConfigSubsection()
{
SomeSubsectionEnumValue = DayOfWeek.Thursday,
SomeSubsectionGuidValue = Guid.Parse("{2C475019-5AAC-43C6-AC87-21947A40E3B7}")
}
};
Now, I need to be able to create, serialize, store and later deserialize and operate on a partial configuration model, like below:
var aPartialConfigInstance = new TestConfigModelNullable()
{
SomeIntValue = 4,
Subsection = new TestConfigSubsection()
{
SomeSubsectionEnumValue = DayOfWeek.Monday
}
};
... with all missing properties null. If I try to do the same with the original class, all the other non-nullable fields will receive default values and that would be bad (how do I tell if int value of 0 is intended or not? Maybe it makes sense for the consumer app).
However, I'm new to reflection in general and not sure how to approach this. Your help would be much appreciated :)
Recall that we don't know the model ahead of time.
I happened to implement several similar mechanisms in several different flavors. Implementing an "automagical" mechanism implies quite a bit of heavy lifting.
Here I wouldn't suggest generating separate nullable versions of the models. Instead, I would opt for making all model properties Optional<T>, which is like Nullable<T> but works for reference types as well. In this way, partial models will be represented with the same types as "base" models.
Such an approach will save the complexity of code generation (T4, Roslyn, CodeDom, or Reflection.Emit -- all these imply a lot of effort, including plugging them into the build process).
In addition, in either approach, a "merging" logic must be implemented which applies a partial model over a "base" one. In code generation approach, the merge logic can be generated as part of the nullable models. In Optional<T> approach, it can be either hard-coded or implemented in generic way with runtime Reflection (not Reflection.Emit). The hard-coded way appears to be the easiest, but for large number of models and properties, runtime Reflection may be a better fit.
How it will look
The models would look like this:
public class TestConfigModel
{
public Optional<int> SomeIntValue { get; set; }
public Optional<string> SomeStringValue { get; set; }
public Optional<TestConfigSubsection> Subsection { get; set; }
}
With the implicit conversion operators of Optional<T>, you'll be able to initialize section values as normally:
var config = new TestConfigModel {
SomeIntValue = 123,
SomeStringValue = "ABC",
Subsection = new TestConfigSubsection {
SomeSubsectionEnumValue = DayOfWeek.Thursday
}
};
Generic merging logic can be implemented by introducing an Apply method to Optional<T>:
Optional<T> Apply(Optional<T> partial, Func<T, T, Optional<T>> merge = null)
Every model will have to implement its own ApplyXxxx() method that will be passed in the merge parameter, like this:
public class TestConfigModel
{
// ...properties
public Optional<TestConfigModel> ApplyModel(TestConfigModel partial)
{
SomeIntValue = SomeIntValue.Apply(partial.SomeIntValue);
SomeStringValue = SomeStringValue.Apply(partial.SomeStringValue);
Subsection = Subsection.Apply(
partial.Subsection,
merge: (left, right) => left.ApplySubsection(right));
return this;
}
}
public class TestConfigSubsection
{
// ...properties
public Optional<TestConfigSubsection> ApplySubsection(TestConfigSubsection partial)
{
SomeSubsectionEnumValue = SomeSubsectionEnumValue.Apply(partial.SomeSubsectionEnumValue);
SomeSubsectionGuidValue = SomeSubsectionGuidValue.Apply(partial.SomeSubsectionGuidValue);
return this;
}
}
Optional<T>
Built-in implementation of Optional<T> is planned for C# 8, but it can be implemented easily (mostly similar to Nullable<T>).
public interface IOptional
{
bool HasValue { get; }
object Value { get; }
}
public struct Optional<T> : IOptional
{
private readonly bool _hasValue;
private readonly T _value;
public Optional(T value)
{
_value = value;
_hasValue = true;
}
public bool HasValue => _hasValue;
object IOptional.Value => Value;
public T Value
{
get
{
if (!_hasValue)
{
throw new InvalidOperationException("has no value");
}
return _value;
}
}
public T GetValueOrDefault() => _value;
public T GetValueOrDefault(T defaultValue)
{
if (!_hasValue)
{
return defaultValue;
}
return _value;
}
public bool IsNullValue => _hasValue && ReferenceEquals(_value, null);
public override bool Equals(object other)
{
if (other is Optional<T> otherOptional)
{
if (_hasValue != otherOptional.HasValue)
{
return false;
}
if (_hasValue)
{
return CompareValue(otherOptional.Value);
}
return true;
}
return false;
}
bool CompareValue(object otherValue)
{
if (_value == null)
{
return (otherValue == null);
}
return _value.Equals(otherValue);
}
public override int GetHashCode()
{
if (_hasValue || ReferenceEquals(_value, null))
{
return 0;
}
return _value.GetHashCode();
}
public override string ToString()
{
if (!_hasValue || ReferenceEquals(_value, null))
{
return "";
}
return _value.ToString();
}
public Optional<T> Apply(Optional<T> partial, Func<T, T, Optional<T>> merge = null)
{
if (!_hasValue && partial.HasValue)
{
return partial;
}
if (_hasValue && partial.HasValue)
{
if (ReferenceEquals(_value, null))
{
return partial.Value;
}
if (!ReferenceEquals(partial.Value, null))
{
if (merge != null)
{
return merge(_value, partial.Value);
}
throw new InvalidOperationException("both values exist and merge not provided");
}
}
return this;
}
public static implicit operator Optional<T>(T value)
{
return new Optional<T>(value);
}
public static explicit operator T(Optional<T> value)
{
return value.Value;
}
}
Serialization
The last thing left is to teach the serializers to handle Optional<T>. For instance, Newtonsoft.Json would require a custom JsonConverter. Below isn't a complete implementation, but it demonstrates the approach:
public class OptionalConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Optional<>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// TODO: implement properly
// roughly the approach is like this:
var hasValue = reader.ReadAsBoolean().GetValueOrDefault();
var innerValue = hasValue
? serializer.Deserialize(reader, objectType.GetGenericArguments([0])
: null;
return Activator.CreateInstance(
objectType,
innerValue != null ? new[] {innerValue} : new object[0]);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is IOptional optional)
{
// TODO: implement writing
}
}
}
// Just for convenience
public Type CreateNullableTypeFrom<T>()
{
return CreateNullableTypeFrom(typeof(T));
}
public Type CreateNullableTypeFrom(Type typeToConvert)
{
// Get the AssemblyName where the type is defined
AssemblyName assembly = typeToConvert.Assembly.GetName();
AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run);
ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule(assembly.Name);
TypeBuilder typeBuilder = dynamicModule.DefineType(typeToConvert.Name + "Nullable");
// Loop through the properties
foreach(PropertyInfo property in typeToConvert.GetProperties())
{
// If property is value type, it can't be null
if(property.PropertyType.IsValueType)
{
// Create a nullable type for the property
typeBuilder.DefineProperty(property.Name, property.Attributes, typeof(Nullable<>).MakeGenericType(property.PropertyType), Type.EmptyTypes);
}
// The property can be null
else
{
// Create a similar property
typeBuilder.DefineProperty(property.Name, property.Attributes, property.PropertyType, Type.EmptyTypes);
}
}
// Finally, create the type
Type convertedType = typeBuilder.CreateType();
Console.WriteLine(convertedType.Name);
// Note: to access the properties of the converted type through reflection,
// use GetRuntimeProperties method, not GetProperties, since GetProperties
// will return an empty array because the type was created an runtime
return convertedType;
}
This is about Data Annotations in C#.
I have a class called MyClass. There I will be having two properties.
class MyClass
{
private string _propName;
public string PropName
{
get {return _propName;}
set {_propName=value;}
}
private string _propVal;
public string PropVal
{
get {return _propVal;}
set {_propVal=value;}
}
}
I have a MS SQl table which contains data for propName and relevant propValue.
I need to have validations using DataAnnotations. Each and every _propName need to have different validations.
My question is : How do I add different validations/data annotations to the class structure I have mentioned above ? I was wondering whether or not there is some way to specify if-else conditions in Data Annotations ?
if "this propertyname"
validation 1
else if "that property name"
validation 1
Can someone help ?
You should be able to something like this:
public class MyValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
switch (validationContext.MemberName)
{
//some logic
}
}
}
The problem is with ValidationAttribute your only going to get the value of the one property, not the combination of the properties, what you can do is combine the PropName and PropVal in a tuple, and then validate that.
This little test app shows a method of achieving what you want.
public class MyClass {
[MyValidation]
public Tuple<string, string> PropValue { get; set; }
public List<ValidationResult> TestValidation() {
var validationContext = new ValidationContext(this, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, results, validateAllProperties: true);
return results;
}
}
public class MyValidationAttribute : ValidationAttribute {
public override bool IsValid(object value) {
var propValue = value as Tuple<string, string>;
if (propValue.Item1 == "Item") {
var setPropValue = propValue.Item2;
//Do you validation
}
return true;
}
}
class Program {
static void Main(string[] args) {
MyClass instance = new MyClass();
instance.PropValue = new Tuple<string, string>("Item", "Value");
var result = instance.TestValidation();
}
}
I have inherited a web api that has lots of enums defined in code, I want to convert them to a view-model class called EnumView so they can be serialized as below...
{Id: value, Name: enumName}
public class EnumView
{
public int Id { get; set; }
public string Name { get; set; }
}
Upon restricting the Generic class to the enum type, I get the warning
Constraint cannot be special class 'System.Enum'
This is the generic converter that I was going to use...
public class EnumViewConverter<T> where T : Enum
{
public static List<EnumView> ConvertToView()
{
List<EnumView> enumViews = new List<EnumView>();
T[] enumValues = (T[])Enum.GetValues(typeof(T));
foreach (var enumValue in enumValues)
{
var enumView = new EnumView
{
Id = (int)enumValue,
Name = Enum.GetName(typeof(T), enumValue)
};
enumViews.Add(enumView);
}
return enumViews;
}
}
Without T being constrained to an enum, the following conversion doesn't compile...
Id = (int)enumValue,
Due to the issue around a lack of generic enum constraints, what's the best way to go about this?
You could use : Id = Convert.ToInt32(enumValue) instead of the casting (int)
And if you want to add some 'constraint' to check the type at the compilation you could set 'where T : struct' it will at least prevent to set class type.
In the end I used...
public static class EnumViewConverter<T> where T : struct
{
public static List<EnumView> ConvertToView()
{
if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
List<EnumView> enumViews = new List<EnumView>();
T[] enumValues = (T[])Enum.GetValues(typeof(T));
foreach (var enumValue in enumValues)
{
var enumView = new EnumView
{
Id = Convert.ToInt32(enumValue),
Name = Enum.GetName(typeof(T), enumValue)
};
enumViews.Add(enumView);
}
return enumViews;
}
}
Called by...
var views = EnumViewConverter<FooBarEnum>.ConvertToView();
Thanks for all the help, could have sworn I tried this earlier:(
To expand on my earlier comment, the technique described in this answer uses a nested class with a generic parameter dependent on its parent class to emulate generic Enum constraints. You could use this approach here:
public abstract class ConverterClassUtils<TClass>
where TClass : class
{
public class ViewConverter<TInner> where TInner : struct, TClass
{
public static List<EnumView> ConvertToView()
{
List<EnumView> enumViews = new List<EnumView>();
TInner[] enumValues = (TInner[])Enum.GetValues(typeof(TInner));
foreach (var enumValue in enumValues)
{
var enumView = new EnumView
{
Id = (int)(object)enumValue,
Name = Enum.GetName(typeof(TInner), enumValue)
};
enumViews.Add(enumView);
}
return enumViews;
}
}
}
public class EnumConverter : ConverterClassUtils<Enum> { }
then the following compiles:
var view = EnumConverter.ViewConverter<SomeEnum>.ConvertToView();
while this does not:
var view = EnumConverter.ViewConverter<int>.ConvertToView();
Is there a way to force the compiler to restrict the usage of a custom attribute to be used only on specific property types like int, short, string (all the primitive types)?
similar to the AttributeUsageAttribute's ValidOn-AttributeTargets enumeration.
No, you can't, basically. You can limit it to struct vs class vs interface, that is about it. Plus: you can't add attributes to types outside your code anyway (except for via TypeDescriptor, which isn't the same).
You can run this unit test to check it.
First, declare validation attribute PropertyType:
[AttributeUsage(AttributeTargets.Class)]
// [JetBrains.Annotations.BaseTypeRequired(typeof(Attribute))] uncomment if you use JetBrains.Annotations
public class PropertyTypeAttribute : Attribute
{
public Type[] Types { get; private set; }
public PropertyTypeAttribute(params Type[] types)
{
Types = types;
}
}
Create unit test:
[TestClass]
public class TestPropertyType
{
public static Type GetNullableUnderlying(Type nullableType)
{
return Nullable.GetUnderlyingType(nullableType) ?? nullableType;
}
[TestMethod]
public void Test_PropertyType()
{
var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes());
var allPropertyInfos = allTypes.SelectMany(a => a.GetProperties()).ToArray();
foreach (var propertyInfo in allPropertyInfos)
{
var propertyType = GetNullableUnderlying(propertyInfo.PropertyType);
foreach (var attribute in propertyInfo.GetCustomAttributes(true))
{
var attributes = attribute.GetType().GetCustomAttributes(true).OfType<PropertyTypeAttribute>();
foreach (var propertyTypeAttr in attributes)
if (!propertyTypeAttr.Types.Contains(propertyType))
throw new Exception(string.Format(
"Property '{0}.{1}' has invalid type: '{2}'. Allowed types for attribute '{3}': {4}",
propertyInfo.DeclaringType,
propertyInfo.Name,
propertyInfo.PropertyType,
attribute.GetType(),
string.Join(",", propertyTypeAttr.Types.Select(x => "'" + x.ToString() + "'"))));
}
}
}
}
Your attribute, for example allow only decimal property types:
[AttributeUsage(AttributeTargets.Property)]
[PropertyType(typeof(decimal))]
public class PriceAttribute : Attribute
{
}
Example model:
public class TestModel
{
[Price]
public decimal Price1 { get; set; } // ok
[Price]
public double Price2 { get; set; } // error
}
You could write code yourself to enforce correct use of your attribute class, but that's as much as you can do.
The code below will return an error if the attribute was placed on a property/field that is not List of string.
The line if (!(value is List<string> list)) may be a C#6 or 7 feature.
[AttributeUsage(AttributeTargets.Property |
AttributeTargets.Field, AllowMultiple = false)]
public sealed class RequiredStringListAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (!(value is List<string> list))
return new ValidationResult($"The required attrribute must be of type List<string>");
bool valid = false;
foreach (var item in list)
{
if (!string.IsNullOrWhiteSpace(item))
valid = true;
}
return valid
? ValidationResult.Success
: new ValidationResult($"This field is required"); ;
}
}
The way I am doing this is following:
[AttributeUsage(AttributeTargets.Property)]
public class SomeValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value is not string stringToValidate)
{
throw new AttributeValueIsNotStringException(validationContext.DisplayName, validationContext.ObjectType.Name);
}
// validationContext.DisplayName is name of property, where validation attribut was used.
// validationContext.ObjectType.Name is name of class, in which the property is placed to instantly identify, where is the error.
//Some validation here.
return ValidationResult.Success;
}
}
And exception look like this:
public class AttributeValueIsNotStringException : Exception
{
public AttributeValueIsNotStringException(string propertyName, string className) : base(CreateMessage(propertyName, className))
{
}
private static string CreateMessage(string propertyName, string className)
{
return $"Validation attribute cannot be used for property: \"{propertyName}\" in class: \"{className}\" because it's type is not string. Use it only for string properties.";
}
}
Is there any way to move the Parse method into the abstract class ? I tried multiple ways (links at the bottom), but I am still hitting one or another roadblock.
public class AnimalEntityId : EntityId<AnimalEntityId>
{
public AnimalEntityId()
: base()
{
}
private AnimalEntityId(string value)
: base(value)
{
}
public static AnimalEntityId Parse(string value)
{
return new AnimalEntityId(value);
}
}
public abstract class EntityId<TEntityId>
{
private readonly System.Guid value;
protected EntityId(string value)
{
this.value = System.Guid.Parse(value);
}
protected EntityId()
{
this.value = System.Guid.NewGuid();
}
}
Tried these suggestions with no luck:
Passing arguments to C# generic new() of templated type
Is there a generic constructor with parameter constraint in C#?
https://social.msdn.microsoft.com/Forums/en-US/fd43d184-0503-4d4a-850c-999ca58e1444/creating-generic-t-with-new-constraint-that-has-parameters?forum=csharplanguage
http://www.gamedev.net/topic/577668-c-new-constraint--is-it-possible-to-add-parameters/
Thanks in advance!
If you don't mind using reflection, you can move Parse into the abstract type like this:
public static TEntityId Parse(string val) {
var constr = typeof(TEntityId).GetConstructor(
// Since the constructor is private, you need binding flags
BindingFlags.Instance | BindingFlags.NonPublic
, null
, new[]{ typeof(string) }
, null);
if (constr == null) {
throw new InvalidOperationException("No constructor");
}
return (TEntityId)constr.Invoke(new object[] {val});
}
Demo.
No, you cannot write a template constraint such as new(string) instead of simply new(). You'll have to leverage reflection to get it to work:
public abstract class EntityId<TEntityId>
where TEntityId : EntityId<TEntityId>
{
private readonly System.Guid value;
protected EntityId(string value)
{
this.value = System.Guid.Parse(value);
}
protected EntityId()
{
this.value = System.Guid.NewGuid();
}
public static TEntityId Parse(string value)
{
return (TEntityId)Activator.CreateInstance(typeof(TEntityId), new object[] { value });
}
}
Assuming you make the constructor accessible (instead of it currently being private). Note the constraint where TEntityId : EntityId<TEntityId> - which will ensure we'll only return subclasses of EntityId
How about making value a private mutable field/property and actually setting it from the Parse method?
(Curiously recurring generic parameter removed from EntityId for simplicity)
public class SimpleAnimalEntityId : EntityId
{
// Implicit parameterless constructor.
}
public class ParametrizedAnimalEntityId : EntityId
{
// Parametrized constructor only.
public ParametrizedAnimalEntityId(int ignored)
{
}
}
public abstract class EntityId
{
// Simple scenario: derived type has a parameterless constructor.
public static TEntity Parse<TEntity>(string value)
where TEntity : EntityId, new()
{
Guid id = Guid.Parse(value);
return new TEntity { value = id };
}
// Advanced scenario: derived type constructor needs parameters injected.
public static TEntity Parse<TEntity>(string value, Func<TEntity> constructor)
where TEntity : EntityId
{
Guid id = Guid.Parse(value);
TEntity entity = constructor();
entity.value = id;
return entity;
}
private Guid value;
protected EntityId()
{
value = Guid.NewGuid();
}
}
Now you can handle any constructor from your Parse method:
string id = Guid.NewGuid().ToString();
SimpleAnimalEntityId simple = EntityId.Parse<SimpleAnimalEntityId>(id);
ParametrizedAnimalEntityId parametrized = EntityId.Parse(id, () => new ParametrizedAnimalEntityId(42));