For some library debugging usage I am developing a custom attribute which whould be able to match existing methods.
For example the following:
[NetMethodAliasMethod("TryParse", Parameters = new Type[] { typeof(string), typeof(char)})]
...should match static bool System.Char.TryParse (string? s, out char result)
. The attribute "Parameters" are tested against ParameterInfo.ParameterType MethodInfo.GetParameters() to match the parameters (of course name is matched as well).
In case of the string?
this works because the nullable attribute is not part of the type. However the type returned from MethodInfo
for the 2nd parameter is char&
, while typeof(char&)
is not allowed. Is there any possible way to pass this type ?
I was thinking about generic delegates, but see no way as the use of delegates is to be specific on which methods are compatible. Another Idea was to use typeof(System.Char).GetMethod("TryParse")
. But this causes the same trouble if multiple overloads exist, where we would have to use GetMethods
(with 's') instead and again match the paramater types.
Of course I could use string checking of the type names, but that should be the last resort if no other way is possible. But since Type(char&)
exists, I hope there is a way to get it ?
CodePudding user response:
I like the elegance of @JeroenMostert comment so I did add a ByRef abstract generic class to my attribute:
public abstract class ByRef<T> { };
and modified the Parameters property to resolve the type
private Type[]? _parameters = null;
public Type[]? Parameters
{
get { return _parameters; }
set
{
if(value==null)
{
_parameters = null;
return;
}
for(int i=0; i<value.Length; i )
{
Type type = value[i];
if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ByRef<>))
{
value[i] = type.GetGenericArguments()[0].MakeByRefType();
}
_parameters = value;
}
}
}
This setter resolves all ByRef to T&
Note that in my case here, Parameters
is nullable because I use null as parameter wildcard (i.e. matching any types and count).
CodePudding user response:
You can make such a type by using the MakeByRefType
method.
However you cannot actually call this method when writing your attribute, because something like typeof(int).MakeByRefType()
is not an attribute argument expression (defined at the very bottom of this section).
Therefore, a workaround I would go for is to declare an additional parameter for your attribute that encodes where these "by refs" are:
public bool[] ParametersAreByRef { get; set; }
And then do:
[NetMethodAliasMethod(
"TryParse",
Parameters = new Type[] { typeof(string), typeof(char)}
ParametersAreByRef = new bool[] { false, true })]
Only when you retrieve the attribute later down the line, do you use MakeByRefType
to recreate the Type
s depending on the values in ParametersAreByRef
.
var modifiedParameters = attribute.Parameters.Zip(
attribute.ParametersAreByRef, (t, b) => b ? t.MakeByRefType() : t
).ToArray()
If you don't like passing ParametersAreByRef
every time for methods with all non-by-ref parameters, you can make Parameters
a positional parameter (put it as a constructor parameter), and then you can initialise ParametersAreByRef
to Parameters.Select(x => false).ToArray()
by default or something like that in the constructor.
As Jeroen Mostert suggested in the comments, another workaround is to use pointer types, seeing as they are very rare in actual code, to encode by-ref-ness:
Parameters = new Type[] { typeof(string), typeof(char*)}
When you retrieve the attribute instance, check IsPointer
and use GetElementType
:
var modifiedParameters = attribute.Parameters.Select(
x => x.IsPointer ? x.GetElementType().MakeByRefType() : x
).ToArray();
You can also do this in the setter of Parameters
if it has a backing field.
Alternatively, make your own dummy ByRef<T>
type so that by-ref-ness can be annotated:
public abstract class ByRef<T> {}
…
Parameters = new Type[] { typeof(string), typeof(ByRef<char>)}
Then
// e.g. in the setter of Parameters
_parameters = Parameters.Select(
x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ByRef<>) ? x.GetGenericArguments()[0].MakeByRefType() : x
).ToArray();