À ma connaissance, une forme restreinte de votre intention est possible en Java et C # grâce à une combinaison d'annotations et de modèle de proxy dynamique (il existe des implémentations intégrées pour les proxys dynamiques en Java et C #).
Version Java
L'annotation:
@Target(ElementType.PARAMETER)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface IntRange {
int min ();
int max ();
}
La classe Wrapper créant l'instance de proxy:
public class Wrapper {
public static Object wrap(Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new MyInvocationHandler(obj));
}
}
Le InvocationHandler servant de contournement à chaque appel de méthode:
public class MyInvocationHandler implements InvocationHandler {
private Object impl;
public MyInvocationHandler(Object obj) {
this.impl = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Annotation[][] parAnnotations = method.getParameterAnnotations();
Annotation[] par = null;
for (int i = 0; i<parAnnotations.length; i++) {
par = parAnnotations[i];
if (par.length > 0) {
for (Annotation anno : par) {
if (anno.annotationType() == IntRange.class) {
IntRange range = ((IntRange) anno);
if ((int)args[i] < range.min() || (int)args[i] > range.max()) {
throw new Throwable("int-Parameter "+(i+1)+" in method \""+method.getName()+"\" must be in Range ("+range.min()+","+range.max()+")");
}
}
}
}
}
return method.invoke(impl, args);
}
}
L'exemple d'interface d'utilisation:
public interface Example {
public void print(@IntRange(min=0,max=100) int num);
}
Méthode principale:
Example e = new Example() {
@Override
public void print(int num) {
System.out.println(num);
}
};
e = (Example)Wrapper.wrap(e);
e.print(-1);
e.print(10);
Production:
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.print(Unknown Source)
at application.Main.main(Main.java:13)
Caused by: java.lang.Throwable: int-Parameter 1 in method "print" must be in Range (0,100)
at application.MyInvocationHandler.invoke(MyInvocationHandler.java:27)
... 2 more
Version C #
L'annotation (en C # appelé attribut):
[AttributeUsage(AttributeTargets.Parameter)]
public class IntRange : Attribute
{
public IntRange(int min, int max)
{
Min = min;
Max = max;
}
public virtual int Min { get; private set; }
public virtual int Max { get; private set; }
}
La sous-classe DynamicObject:
public class DynamicProxy : DynamicObject
{
readonly object _target;
public DynamicProxy(object target)
{
_target = target;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
TypeInfo clazz = (TypeInfo) _target.GetType();
MethodInfo method = clazz.GetDeclaredMethod(binder.Name);
ParameterInfo[] paramInfo = method.GetParameters();
for (int i = 0; i < paramInfo.Count(); i++)
{
IEnumerable<Attribute> attributes = paramInfo[i].GetCustomAttributes();
foreach (Attribute attr in attributes)
{
if (attr is IntRange)
{
IntRange range = attr as IntRange;
if ((int) args[i] < range.Min || (int) args[i] > range.Max)
throw new AccessViolationException("int-Parameter " + (i+1) + " in method \"" + method.Name + "\" must be in Range (" + range.Min + "," + range.Max + ")");
}
}
}
result = _target.GetType().InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, _target, args);
return true;
}
}
La classe d'exemple:
public class ExampleClass
{
public void PrintNum([IntRange(0,100)] int num)
{
Console.WriteLine(num.ToString());
}
}
Usage:
static void Main(string[] args)
{
dynamic myObj = new DynamicProxy(new ExampleClass());
myObj.PrintNum(99);
myObj.PrintNum(-5);
}
En conclusion, vous voyez que vous pouvez faire fonctionner quelque chose comme ça en Java , mais ce n'est pas tout à fait pratique, car
- La classe proxy peut simplement être instanciée pour les interfaces, c'est-à-dire que votre classe doit implémenter une interface
- La plage autorisée ne peut être déclarée qu'au niveau de l'interface
- Une utilisation ultérieure vient juste avec un effort supplémentaire au début (MyInvocationHandler, encapsulant à chaque instanciation), ce qui réduit également légèrement la compréhension
Les capacités de la classe DynamicObject en C # suppriment la restriction d'interface, comme vous le voyez dans l'implémentation C #. Malheureusement, ce comportement dynamique supprime la sécurité de type statique dans ce cas, des vérifications d'exécution sont donc nécessaires pour déterminer si un appel de méthode sur le proxy dynamique est autorisé.
Si ces restrictions sont acceptables pour vous, cela peut servir de base à des recherches supplémentaires!