I'm implementing Interceptor mechanism in .NET with Castle.DynamicProxy (Castle.Core 4.4.0). I'm following this tutorial for selecting which method to intercept: https://kozmic.net/2009/01/17/castle-dynamic-proxy-tutorial-part-iii-selecting-which-methods-to/
It was given an example in this article about "selecting which methods to intercept":
public class FreezableProxyGenerationHook:IProxyGenerationHook
{
public bool ShouldInterceptMethod(Type type, MethodInfo memberInfo)
{
return !memberInfo.Name.StartsWith("get_", StringComparison.Ordinal);
}
//implementing other methods...
}
According to this article, I implemented the ShouldInterceptMethod like below but i can not access the method's custom attributes.
public class ProductServiceProxyGenerationHook : IProxyGenerationHook
{
public void MethodsInspected()
{
}
public void NonProxyableMemberNotification(Type type, MemberInfo memberInfo)
{
}
public bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
{
//return methodInfo.CustomAttributes.Any(a => a.GetType() == typeof(UseInterceptorAttribute));
return methodInfo.CustomAttributes.Count() > 0;
}
}
This is the method that i want to intercept:
[UseInterceptor]
public Product AOP_Get(string serialNumber)
{
throw new NotImplementedException();
}
This is my custom attribute:
[System.AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
sealed class UseInterceptorAttribute : Attribute
{
public UseInterceptorAttribute()
{
}
}
When ShouldInterceptMethod invoked for AOP_Get method, there aren't any custom attributes on local variable methodInfo. As a result ShouldInterceptMethod returns false. But when i check from the AOP_Get method body, i can access custom attribute like below:
How can i access custom attributes in ShouldInterceptMethod method?
I solved the problem using UseInterceptorAttribute in the interface methods. Because the interceptor is enabling for interface:
//autofac
var proxyGenerationOptions = new ProxyGenerationOptions(new ProductServiceProxyGenerationHook());
builder.RegisterType<ProductService>()
.As<IProductService>()
.EnableInterfaceInterceptors(proxyGenerationOptions)
.InterceptedBy(typeof(LoggingInterceptor));
The solution:
public interface IProductService
{
Product Get(string productNumber, string serialNumber);
bool Create(string productNumber, string serialNumber);
[UseInterceptor]
Product AOP_Get(string productNumber, string serialNumber);
}
public class ProductService : IProductService
{
public Product AOP_Get(string productNumber, string serialNumber)
{
var m = GetType().GetMethod("AOP_Get");
return null;
}
public bool Create(string productNumber, string serialNumber)
{
return true;
}
public Product Get(string productNumber, string serialNumber)
{
return new Product
{
Id = 5,
ProductNumber = "testPN",
SerialNumber = "testSN"
};
}
}
public class ProductServiceProxyGenerationHook : IProxyGenerationHook
{
public void MethodsInspected()
{
}
public void NonProxyableMemberNotification(Type type, MemberInfo memberInfo)
{
}
public bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
{
return methodInfo
.CustomAttributes
.Any(a => a.AttributeType == typeof(UseInterceptorAttribute));
}
}
}
Related
I use FluentValidation and PostSharp in my business layer.
My database has an Admin table, and the UserName column in this table is unique.
I want to check the uniqueness with an "Aspect".
My codes are as follows.
AdminValidator
public class AdminValidator : AbstractValidator<Admin>
{
public AdminValidator(IEnumerable<Admin> admins)
{
RuleFor(x => x.Fullname).MaximumLength(50);
RuleFor(x => x.Username).Matches(#"^\S{3,15}$").IsUnique(admins);
RuleFor(x => x.Password).Matches(#"^\S{5,20}$");
}
}
IsUnique extension method
public static class Extensions
{
public static IRuleBuilderOptions<TItem, TProperty> IsUnique<TItem, TProperty>(
this IRuleBuilder<TItem, TProperty> ruleBuilder, IEnumerable<TItem> items)
where TItem : class
{
return ruleBuilder.SetValidator(new UniqueValidator<TItem>(items));
}
}
public class UniqueValidator<T> : PropertyValidator
where T : class
{
private readonly IEnumerable<T> _items;
public UniqueValidator(IEnumerable<T> items)
: base("{PropertyName} must be unique")
{
_items = items;
}
protected override bool IsValid(PropertyValidatorContext context)
{
var editedItem = context.Instance as T;
var newValue = context.PropertyValue as string;
var property = typeof(T).GetTypeInfo().GetDeclaredProperty(context.PropertyName);
return _items.All(item =>
item.Equals(editedItem) || property.GetValue(item).ToString() != newValue);
}
}
FluentValidation aspect
[Serializable]
public class FluentValidationAspect : OnMethodBoundaryAspect
{
private readonly Type _validatorType;
private readonly object[] _parameters;
public FluentValidationAspect(Type validatorType, params object[] parameters)
{
_validatorType = validatorType;
_parameters = parameters;
}
public override void OnEntry(MethodExecutionArgs args)
{
var validator = (IValidator)Activator.CreateInstance(_validatorType, _parameters);
var entityType = _validatorType.BaseType?.GetGenericArguments()[0];
var entities = args.Arguments.Where(x => x.GetType() == entityType);
foreach (var entity in entities)
ValidatorTool.FluentValidate(validator, entity);
}
}
and AdminManager
public class AdminManager : IAdminService
{
private readonly IAdminDal _adminDal;
public AdminManager(IAdminDal adminDal)
{
_adminDal = adminDal;
}
public Admin GetByUsername(string username)
=> string.IsNullOrEmpty(username) ? null : _adminDal.Get(x => x.Username.Equals(username));
[FluentValidationAspect(typeof(AdminValidator))] // I need to pass IEnumerable<Admin> as a second parameter.
public void Add(Admin admin) => _adminDal.Add(admin);
[FluentValidationAspect(typeof(AdminValidator))] //
public void Update(Admin admin) => _adminDal.Update(admin);
public void DeleteById(int id) => _adminDal.Delete(new Admin { Id = id });
}
As I mentioned in the comment line, I need to pass IEnumerable to the FluentValidationAspect.
But, dynamic parameters can not be passed to attributes.
As a result, I'm blocked here.
What is the best way to check for uniqueness?
Thank you in advance for your help?
Best regards...
You can't pass dynamic values in Attributes, so you will have to do the validation in the method body.
public void Add(Admin admin)
{
var admins = GetAll():
var val = new AdminValidator(admins);
validator.ValidateAndThrow(admin);
_adminDal.Add(admin);
}
I'm developing a input validation library for my APIs.
interface IValidation<T>{
List<string> Errors();
bool Valid(T input);
}
class GuidValidator: IValidation<Guid> {
bool Valid(Guid input){ ... }
}
class StringLengthValidator: IValidation<String> {
bool Valid(String input){ ... }
}
and am trying to validate
class MyInputToValidate {
public Guid Id {get;set;}
public string Name {get;set;}
public string Description {get;set;}
}
from the controller
class MyController {
public ActionResult Post([FromBody] MyInputToValidate body){
ValidatorCollection validators = new ValidatorCollection<MyInputToValidate>();
validators.AddValidator(i => i.Id, new GuidValidator());
validators.AddValidator(i => i.Name, new LengthValidator());
validators.AddValidator(i => i.Description, new LengthValidator());
bool valid = validators.Validate(body);
//...
}
}
But I'm having difficulty saving the validators to a dictionary for later use.
interface IValidatorCollection<T> {
void AddValidator<TProperty>(
Expression<Func<T, TProperty>> property,
IValidator<TProperty> validator
);
bool Validate(T value);
}
class ValidatorCollection: IValidatorCollection<T> {
private IDictionary<string, IValidator<object>> _validators = new Dictionary<string, IValidator<object>>();
public void AddValidator<TProperty>(Expression<Func<T, TProperty>> property, IValidator<TProperty> validator)
{
_validators.Add(property.Name, validator);
}
public bool Validate(T value)
{
foreach(var val in _validators){
bool valid = val.Value.Valid(value);
}
//...
}
}
I receive the error IValidator<TProperty> is not assignable to to IValidator<object> for the line _validators.Add(property.Name, validator);. I've tried typecasting (IValidator<object>validator), but I receive the runtime error Unable to cast object GuidValidator to type IValidator<object>. How can I store a list of validators for the properties of a generic object?
I've just started to use AutoFixture.AutoMoq in my unit tests and I'm finding it very helpful for creating objects where I don't care about the specific value. After all, anonymous object creation is what it is all about.
What I'm struggling with is when I care about one or more of the constructor parameters. Take ExampleComponent below:
public class ExampleComponent
{
public ExampleComponent(IService service, string someValue)
{
}
}
I want to write a test where I supply a specific value for someValue but leave IService to be created automatically by AutoFixture.AutoMoq.
I know how to use Freeze on my IFixture to keep hold of a known value that will be injected into a component but I can't quite see how to supply a known value of my own.
Here is what I would ideally like to do:
[TestMethod]
public void Create_ExampleComponent_With_Known_SomeValue()
{
// create a fixture that supports automocking
IFixture fixture = new Fixture().Customize(new AutoMoqCustomization());
// supply a known value for someValue (this method doesn't exist)
string knownValue = fixture.Freeze<string>("My known value");
// create an ExampleComponent with my known value injected
// but without bothering about the IService parameter
ExampleComponent component = this.fixture.Create<ExampleComponent>();
// exercise component knowning it has my known value injected
...
}
I know I could do this by calling the constructor directly but this would no longer be anonymous object creation. Is there a way to use AutoFixture.AutoMock like this or do I need to incorporate a DI container into my tests to be able to do what I want?
EDIT:
I probably should have been less absract in my original question so here is my specific scenario.
I have an ICache interface which has generic TryRead<T> and Write<T> methods:
public interface ICache
{
bool TryRead<T>(string key, out T value);
void Write<T>(string key, T value);
// other methods not shown...
}
I'm implementing a CookieCache where ITypeConverter handles converting objects to and from strings and lifespan is used to set the expiry date of a cookie.
public class CookieCache : ICache
{
public CookieCache(ITypeConverter converter, TimeSpan lifespan)
{
// usual storing of parameters
}
public bool TryRead<T>(string key, out T result)
{
// read the cookie value as string and convert it to the target type
}
public void Write<T>(string key, T value)
{
// write the value to a cookie, converted to a string
// set the expiry date of the cookie using the lifespan
}
// other methods not shown...
}
So when writing a test for the expiry date of a cookie, I care about the lifespan but not so much about the converter.
So I'm sure people could work out the generalized implementation of Mark's suggestion but I thought I'd post it for comments.
I've created a generic ParameterNameSpecimenBuilder based on Mark's LifeSpanArg:
public class ParameterNameSpecimenBuilder<T> : ISpecimenBuilder
{
private readonly string name;
private readonly T value;
public ParameterNameSpecimenBuilder(string name, T value)
{
// we don't want a null name but we might want a null value
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException("name");
}
this.name = name;
this.value = value;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
{
return new NoSpecimen(request);
}
if (pi.ParameterType != typeof(T) ||
!string.Equals(
pi.Name,
this.name,
StringComparison.CurrentCultureIgnoreCase))
{
return new NoSpecimen(request);
}
return this.value;
}
}
I've then defined a generic FreezeByName extension method on IFixture which sets the customization:
public static class FreezeByNameExtension
{
public static void FreezeByName<T>(this IFixture fixture, string name, T value)
{
fixture.Customizations.Add(new ParameterNameSpecimenBuilder<T>(name, value));
}
}
The following test will now pass:
[TestMethod]
public void FreezeByName_Sets_Value1_And_Value2_Independently()
{
//// Arrange
IFixture arrangeFixture = new Fixture();
string myValue1 = arrangeFixture.Create<string>();
string myValue2 = arrangeFixture.Create<string>();
IFixture sutFixture = new Fixture();
sutFixture.FreezeByName("value1", myValue1);
sutFixture.FreezeByName("value2", myValue2);
//// Act
TestClass<string> result = sutFixture.Create<TestClass<string>>();
//// Assert
Assert.AreEqual(myValue1, result.Value1);
Assert.AreEqual(myValue2, result.Value2);
}
public class TestClass<T>
{
public TestClass(T value1, T value2)
{
this.Value1 = value1;
this.Value2 = value2;
}
public T Value1 { get; private set; }
public T Value2 { get; private set; }
}
You have to replace:
string knownValue = fixture.Freeze<string>("My known value");
with:
fixture.Inject("My known value");
You can read more about Inject here.
Actually the Freeze extension method does:
var value = fixture.Create<T>();
fixture.Inject(value);
return value;
Which means that the overload you used in the test actually called Create<T> with a seed: My known value resulting in "My known value4d41f94f-1fc9-4115-9f29-e50bc2b4ba5e".
You could do something like this. Imagine that you want to assign a particular value to the TimeSpan argument called lifespan.
public class LifespanArg : ISpecimenBuilder
{
private readonly TimeSpan lifespan;
public LifespanArg(TimeSpan lifespan)
{
this.lifespan = lifespan;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
return new NoSpecimen(request);
if (pi.ParameterType != typeof(TimeSpan) ||
pi.Name != "lifespan")
return new NoSpecimen(request);
return this.lifespan;
}
}
Imperatively, it could be used like this:
var fixture = new Fixture();
fixture.Customizations.Add(new LifespanArg(mySpecialLifespanValue));
var sut = fixture.Create<CookieCache>();
This approach can be generalized to some degree, but in the end, we're limited by the lack of a strongly typed way to extract a ParameterInfo from a particular constructor or method argument.
I fee like #Nick was almost there. When overriding the constructor argument, it needs to be for the given type and have it limited to that type only.
First we create a new ISpecimenBuilder that looks at the "Member.DeclaringType" to keep the correct scope.
public class ConstructorArgumentRelay<TTarget,TValueType> : ISpecimenBuilder
{
private readonly string _paramName;
private readonly TValueType _value;
public ConstructorArgumentRelay(string ParamName, TValueType value)
{
_paramName = ParamName;
_value = value;
}
public object Create(object request, ISpecimenContext context)
{
if (context == null)
throw new ArgumentNullException("context");
ParameterInfo parameter = request as ParameterInfo;
if (parameter == null)
return (object)new NoSpecimen(request);
if (parameter.Member.DeclaringType != typeof(TTarget) ||
parameter.Member.MemberType != MemberTypes.Constructor ||
parameter.ParameterType != typeof(TValueType) ||
parameter.Name != _paramName)
return (object)new NoSpecimen(request);
return _value;
}
}
Next we create an extension method to allow us to easily wire it up with AutoFixture.
public static class AutoFixtureExtensions
{
public static IFixture ConstructorArgumentFor<TTargetType, TValueType>(
this IFixture fixture,
string paramName,
TValueType value)
{
fixture.Customizations.Add(
new ConstructorArgumentRelay<TTargetType, TValueType>(paramName, value)
);
return fixture;
}
}
Now we create two similar classes to test with.
public class TestClass<T>
{
public TestClass(T value1, T value2)
{
Value1 = value1;
Value2 = value2;
}
public T Value1 { get; private set; }
public T Value2 { get; private set; }
}
public class SimilarClass<T>
{
public SimilarClass(T value1, T value2)
{
Value1 = value1;
Value2 = value2;
}
public T Value1 { get; private set; }
public T Value2 { get; private set; }
}
Finally we test it with an extension of the original test to see that it will not override similarly named and typed constructor arguments.
[TestFixture]
public class AutoFixtureTests
{
[Test]
public void Can_Create_Class_With_Specific_Parameter_Value()
{
string wanted = "This is the first string";
string wanted2 = "This is the second string";
Fixture fixture = new Fixture();
fixture.ConstructorArgumentFor<TestClass<string>, string>("value1", wanted)
.ConstructorArgumentFor<TestClass<string>, string>("value2", wanted2);
TestClass<string> t = fixture.Create<TestClass<string>>();
SimilarClass<string> s = fixture.Create<SimilarClass<string>>();
Assert.AreEqual(wanted,t.Value1);
Assert.AreEqual(wanted2,t.Value2);
Assert.AreNotEqual(wanted,s.Value1);
Assert.AreNotEqual(wanted2,s.Value2);
}
}
This seems to be the most comprehensive solution set here. So I'm going to add mine:
The first thing to create ISpecimenBuilder that can handle multiple constructor parameters
internal sealed class CustomConstructorBuilder<T> : ISpecimenBuilder
{
private readonly Dictionary<string, object> _ctorParameters = new Dictionary<string, object>();
public object Create(object request, ISpecimenContext context)
{
var type = typeof (T);
var sr = request as SeededRequest;
if (sr == null || !sr.Request.Equals(type))
{
return new NoSpecimen(request);
}
var ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault();
if (ctor == null)
{
return new NoSpecimen(request);
}
var values = new List<object>();
foreach (var parameter in ctor.GetParameters())
{
if (_ctorParameters.ContainsKey(parameter.Name))
{
values.Add(_ctorParameters[parameter.Name]);
}
else
{
values.Add(context.Resolve(parameter.ParameterType));
}
}
return ctor.Invoke(BindingFlags.CreateInstance, null, values.ToArray(), CultureInfo.InvariantCulture);
}
public void Addparameter(string paramName, object val)
{
_ctorParameters.Add(paramName, val);
}
}
Then create extension method that simplifies usage of created builder
public static class AutoFixtureExtensions
{
public static void FreezeActivator<T>(this IFixture fixture, object parameters)
{
var builder = new CustomConstructorBuilder<T>();
foreach (var prop in parameters.GetType().GetProperties())
{
builder.Addparameter(prop.Name, prop.GetValue(parameters));
}
fixture.Customize<T>(x => builder);
}
}
And usage:
var f = new Fixture();
f.FreezeActivator<UserInfo>(new { privateId = 15, parentId = (long?)33 });
Good thread, I added another twist based on many of the aswers already posted:
Usage
Example:
var sut = new Fixture()
.For<AClass>()
.Set("value1").To(aInterface)
.Set("value2").ToEnumerableOf(22, 33)
.Create();
Test classes:
public class AClass
{
public AInterface Value1 { get; private set; }
public IEnumerable<int> Value2 { get; private set; }
public AClass(AInterface value1, IEnumerable<int> value2)
{
Value1 = value1;
Value2 = value2;
}
}
public interface AInterface
{
}
Full test
public class ATest
{
[Theory, AutoNSubstituteData]
public void ATestMethod(AInterface aInterface)
{
var sut = new Fixture()
.For<AClass>()
.Set("value1").To(aInterface)
.Set("value2").ToEnumerableOf(22, 33)
.Create();
Assert.True(ReferenceEquals(aInterface, sut.Value1));
Assert.Equal(2, sut.Value2.Count());
Assert.Equal(22, sut.Value2.ElementAt(0));
Assert.Equal(33, sut.Value2.ElementAt(1));
}
}
Infrastructure
Extension method:
public static class AutoFixtureExtensions
{
public static SetCreateProvider<TTypeToConstruct> For<TTypeToConstruct>(this IFixture fixture)
{
return new SetCreateProvider<TTypeToConstruct>(fixture);
}
}
Classes participating in the fluent style:
public class SetCreateProvider<TTypeToConstruct>
{
private readonly IFixture _fixture;
public SetCreateProvider(IFixture fixture)
{
_fixture = fixture;
}
public SetProvider<TTypeToConstruct> Set(string parameterName)
{
return new SetProvider<TTypeToConstruct>(this, parameterName);
}
public TTypeToConstruct Create()
{
var instance = _fixture.Create<TTypeToConstruct>();
return instance;
}
internal void AddConstructorParameter<TTypeOfParam>(ConstructorParameterRelay<TTypeToConstruct, TTypeOfParam> constructorParameter)
{
_fixture.Customizations.Add(constructorParameter);
}
}
public class SetProvider<TTypeToConstruct>
{
private readonly string _parameterName;
private readonly SetCreateProvider<TTypeToConstruct> _father;
public SetProvider(SetCreateProvider<TTypeToConstruct> father, string parameterName)
{
_parameterName = parameterName;
_father = father;
}
public SetCreateProvider<TTypeToConstruct> To<TTypeOfParam>(TTypeOfParam parameterValue)
{
var constructorParameter = new ConstructorParameterRelay<TTypeToConstruct, TTypeOfParam>(_parameterName, parameterValue);
_father.AddConstructorParameter(constructorParameter);
return _father;
}
public SetCreateProvider<TTypeToConstruct> ToEnumerableOf<TTypeOfParam>(params TTypeOfParam[] parametersValues)
{
IEnumerable<TTypeOfParam> actualParamValue = parametersValues;
var constructorParameter = new ConstructorParameterRelay<TTypeToConstruct, IEnumerable<TTypeOfParam>>(_parameterName, actualParamValue);
_father.AddConstructorParameter(constructorParameter);
return _father;
}
}
Constructor parameter relay from other answers:
public class ConstructorParameterRelay<TTypeToConstruct, TValueType> : ISpecimenBuilder
{
private readonly string _paramName;
private readonly TValueType _paramValue;
public ConstructorParameterRelay(string paramName, TValueType paramValue)
{
_paramName = paramName;
_paramValue = paramValue;
}
public object Create(object request, ISpecimenContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
ParameterInfo parameter = request as ParameterInfo;
if (parameter == null)
return new NoSpecimen();
if (parameter.Member.DeclaringType != typeof(TTypeToConstruct) ||
parameter.Member.MemberType != MemberTypes.Constructor ||
parameter.ParameterType != typeof(TValueType) ||
parameter.Name != _paramName)
return new NoSpecimen();
return _paramValue;
}
}
Is it possible to have a delegate as the parameter of an attribute?
Like this:
public delegate IPropertySet ConnectionPropertiesDelegate();
public static class TestDelegate
{
public static IPropertySet GetConnection()
{
return new PropertySetClass();
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface,AllowMultiple=false,Inherited=true)]
public class WorkspaceAttribute : Attribute
{
public ConnectionPropertiesDelegate ConnectionDelegate { get; set; }
public WorkspaceAttribute(ConnectionPropertiesDelegate connectionDelegate)
{
ConnectionDelegate = connectionDelegate;
}
}
[Workspace(TestDelegate.GetConnection)]
public class Test
{
}
And if its not possible, what are the sensible alternatives?
No, you cannot have a delegate as an attribute constructor parameter. See available types: Attribute parameter types
As a workaround (although it's hacky and error prone) you can create a delegate with reflection:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = true)]
public class WorkspaceAttribute : Attribute
{
public ConnectionPropertiesDelegate ConnectionDelegate { get; set; }
public WorkspaceAttribute(Type delegateType, string delegateName)
{
ConnectionDelegate = (ConnectionPropertiesDelegate)Delegate.CreateDelegate(typeof(ConnectionPropertiesDelegate), delegateType, delegateName);
}
}
[Workspace(typeof(TestDelegate), "GetConnection")]
public class Test
{
}
Other possible workaround is creating abstract base Attribute type with abstract method matching your delegate definition, and then implementing the method in concrete Attribute class.
It has following benefits:
Annotation is more concise and clean (DSL like)
No reflection
Easy to reuse
Example:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple=false, Inherited=true)]
public abstract class GetConnectionAttribute : Attribute
{
public abstract IPropertySet GetConnection();
}
public class GetConnectionFromPropertySetAttribute : GetConnectionAttribute
{
public override IPropertySet GetConnection()
{
return new PropertySetClass();
}
}
[GetConnectionFromPropertySet]
public class Test
{
}
I solved this by using an enum and a mapping array of delegates. Although I really like the idea of using inheritance, in my scenario that would require me to write several child classes to do relatively simple stuff. This should be refactorable as well. The only drawback is that you have to make sure to make the delegate's index in the array corresponds to the enum value.
public delegate string FormatterFunc(string val);
public enum Formatter
{
None,
PhoneNumberFormatter
}
public static readonly FormatterFunc[] FormatterMappings = { null, PhoneNumberFormatter };
public string SomeFunction(string zzz)
{
//The property in the attribute is named CustomFormatter
return FormatterMappings[(int)YourAttributeHere.CustomFormatter](zzz);
}
Necromancing.
Augmented on the accepted answer to use a dynamic delegate type:
namespace NetStandardReporting
{
// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = true)]
public class DynamicDllImportAttribute
: System.Attribute
{
protected string m_name;
public string Name
{
get
{
return this.m_name;
}
}
public DynamicDllImportAttribute(string name)
: base()
{
this.m_name = name;
}
private static System.Type CreateDelegateType(System.Reflection.MethodInfo methodInfo)
{
System.Func<System.Type[], System.Type> getType;
bool isAction = methodInfo.ReturnType.Equals((typeof(void)));
System.Reflection.ParameterInfo[] pis = methodInfo.GetParameters();
System.Type[] types = new System.Type[pis.Length + (isAction ? 0: 1)];
for (int i = 0; i < pis.Length; ++i)
{
types[i] = pis[i].ParameterType;
}
if (isAction)
{
getType = System.Linq.Expressions.Expression.GetActionType;
}
else
{
getType = System.Linq.Expressions.Expression.GetFuncType;
types[pis.Length] = methodInfo.ReturnType;
}
return getType(types);
}
private static System.Delegate CreateDelegate(System.Reflection.MethodInfo methodInfo, object target)
{
System.Type tDelegate = CreateDelegateType(methodInfo);
if(target != null)
return System.Delegate.CreateDelegate(tDelegate, target, methodInfo.Name);
return System.Delegate.CreateDelegate(tDelegate, methodInfo);
}
// protected delegate string getName_t();
public DynamicDllImportAttribute(System.Type classType, string delegateName)
: base()
{
System.Reflection.MethodInfo mi = classType.GetMethod(delegateName,
System.Reflection.BindingFlags.Static
| System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic
);
// getName_t getName = (getName_t)System.Delegate.CreateDelegate(delegateType, mi));
System.Delegate getName = CreateDelegate(mi, null);
object name = getName.DynamicInvoke(null);
this.m_name = System.Convert.ToString(name);
}
} // End Class DynamicDllImportAttribute
public class DynamicDllImportTest
{
private static string GetFreetypeName()
{
if (System.Environment.OSVersion.Platform == System.PlatformID.Unix)
return "libfreetype.so.6";
return "freetype6.dll";
}
// [DynamicDllImportAttribute("freetype6")]
[DynamicDllImportAttribute(typeof(DynamicDllImportTest), "GetFreetypeName")]
public static string bar()
{
return "foobar";
}
// NetStandardReporting.DynamicDllImportTest.Test();
public static void Test()
{
System.Reflection.MethodInfo mi = typeof(DynamicDllImportTest).GetMethod("bar",
System.Reflection.BindingFlags.Static
| System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic);
object[] attrs = mi.GetCustomAttributes(true);
foreach (object attr in attrs)
{
DynamicDllImportAttribute importAttr = attr as DynamicDllImportAttribute;
if (importAttr != null)
{
System.Console.WriteLine(importAttr.Name);
}
} // Next attr
} // End Sub Test
} // End Class
} // End Namespace
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));