2010-04-29 4 views
5

J'écris un système qui sous-tend les applications de programmeurs et qui a besoin de détecter leur accès à certaines données. Je peux le faire la plupart du temps avec des propriétés, comme ceci:Une manière "élégante" d'identifier un champ?

public class NiceClass { 
    public int x { get; set; } 
} 

Ensuite, je vais et tweak les get et set accesseurs afin qu'ils gèrent les accès de manière appropriée. Cependant, cela nécessite que les utilisateurs (programmeurs d'applications) définissent toutes leurs données en tant que propriétés.

Si les utilisateurs veulent utiliser des classes préexistantes qui ont des champs «normaux» (par opposition aux propriétés), je ne peux pas détecter ces accès. Exemple:

public class NotSoNiceClass { 
    public int y; 
} 

Je ne peux pas détecter les accès à y. Cependant, je veux autoriser l'utilisation de classes préexistantes. En tant que compromis les utilisateurs sont chargés de me notifier chaque fois qu'un accès à ce genre de données se produit. Par exemple:

NotSoNiceClass notSoNice; 
... 
Write(notSoNice.y, 0); // (as opposed to notSoNice.y = 0;) 

Quelque chose comme ça. Croyez-moi, je l'ai étudié très minutieusement et même en analysant directement le bytecode pour détecter les accès n'est pas fiable en raison des indirections possibles, etc. J'ai vraiment besoin des utilisateurs pour me prévenir.

Et maintenant ma question: pourriez-vous recommander une façon «élégante» d'effectuer ces notifications? (Oui, je sais que toute cette situation n'est pas "élégante" pour commencer, j'essaie de ne pas l'aggraver;)). Comment le feriez-vous?

Ceci est un problème pour moi parce qu'en fait la situation est comme ça: je la classe suivante:

public class SemiNiceClass { 
    public NotSoNiceClass notSoNice { get; set; } 
    public int z { get; set; } 
} 

Si l'utilisateur veut faire:

SemiNiceClass semiNice; 
... 
semiNice.notSoNice.y = 0; 

Ils doivent plutôt faire quelque chose comme ceci:

semiNice.Write("notSoNice").y = 0; 

Write retournera un clone de notSoNice, qui est ce que je voulais que l'accesseur set fasse de toute façon. Cependant, l'utilisation d'une chaîne est plutôt moche: si plus tard, ils refactorisent le champ, ils devront passer par-dessus leurs accès Write("notSoNice") et changer la chaîne.

Comment pouvons-nous identifier le champ? Je ne peux penser qu'à des cordes, des ints et des enums (c'est-à-dire, ints à nouveau). Mais:

  • Nous avons déjà discuté du problème avec les chaînes.
  • Ints sont une douleur. Ils sont encore pires car l'utilisateur doit se rappeler quel int correspond à quel champ. Le refactoring est également difficile.
  • énumérations (tels que NOT_SO_NICE et Z, à savoir, les champs de SemiNiceClass) facilité refactoring, mais ils exigent que l'utilisateur d'écrire un ENUM par classe (SemiNiceClass, etc.), avec une valeur par champ de la classe. C'est ennuyant. Je ne veux pas qu'ils me détestent;)

Alors pourquoi, je vous entends demander, pouvons-nous ne pas le faire (ci-dessous)?

semiNice.Write(semiNice.notSoNice).y = 0; 

Parce que je dois savoir ce champ est accessible, et semiNice.notSoNice ne permettent pas d'identifier un champ. C'est la valeur du champ, pas le champ lui-même.

Soupir. Je sais que c'est moche. Croyez-moi;)

J'apprécierai grandement les suggestions.

Merci d'avance!

(Aussi, je ne pouvais pas trouver de bonnes balises pour cette question S'il vous plaît laissez-moi savoir si vous avez de meilleures idées, et je vais les modifier.)


EDIT # 1: Suggestions de Hightechrider: Expressions.

Je ne sais pas si Write(x =>semiNice.y, 0) était censé être basé sur les classes que j'ai écrites pour ma question (SemiNiceClass, etc) ou si c'est juste un exemple, mais si c'est le premier, il ne correspond pas à la structure: il y a pas de champ y dans SemiNiceClass. Vouliez-vous dire Write(x =>semiNice.notSoNice.y, 0)?

Je ne sais pas comment vous dire pour moi d'utiliser cette ... Je vais poster le code que j'ai écrit:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace Test.MagicStrings { 

    public class MagicStringsTest { 
     public static void Main(string[] args) { 
      SemiNiceClass semiNice = new SemiNiceClass(); 
      // The user wants to do: semiNice.notSoNice.y = 50; 
      semiNice.Write(x => semiNice.notSoNice.y, 50); 
     } 
    } 

    public class SemiNiceClass { 

     public NotSoNiceClass notSoNice { get; set; } 
     public int z { get; set; } 

     public SemiNiceClass() { 
      notSoNice = new NotSoNiceClass(); 
     } 

     public void Write(Func<object, object> accessor, object value) { 
      // Here I'd like to know what field (y, in our example) the user wants to 
      // write to. 
     } 

    } 

    public class NotSoNiceClass { 
     public int y; 
    } 

} 

Comment puis-je obtenir cette information en Write? Je ne trouve aucun moyen d'extraire cette information sur le Func<,>. En outre, pourquoi écrire semiNice.Write(x => semiNice.notSoNice.y, 50); au lieu de semiNice.Write(() => semiNice.notSoNice.y, 50);, puisque nous n'utilisons pas x pour quelque chose?

Merci.


EDIT # 2: suggestion de Hans Passant: remplacer les champs avec des propriétés.

C'est ce que j'avais l'intention de faire à l'origine, mais la recompilation n'est pas une option.


EDIT # 3: la suggestion de Ben Hoffstein: procurations dynamiques; LinFu.

J'ai déjà regardé dans ce long et dur, et pour des raisons relativement complexes je ne peux pas l'utiliser. Ce serait trop long à expliquer, mais rassurez-vous: si je pouvais l'utiliser, je le ferais. C'est beaucoup, beaucoup plus propre que ma solution actuelle.

Répondre

2

Utilisez une expression, par ex. Write (x => semiNice.y, 0)

Cette technique est souvent utilisée pour éviter les chaînes magiques.

par exemple.

public void Write<T,U>(T source, Expression<Func<T, U>> lambda, U value) 
    { 
     var body = lambda.Body as MemberExpression; 
     string memberName = body.Member.Name; 
     if (body.Member.MemberType == MemberTypes.Field) 
     { 
      (body.Member as FieldInfo).SetValue(source, value); 
     } 
     else if (body.Member.MemberType == MemberTypes.Method) 
     { 
      (body.Member as MethodInfo).Invoke(source, new object[]{value}); 
     } 
     Console.WriteLine("You wrote to " + memberName + " value " + value); 
    } 
+0

J'ai mis à jour ma question. Voir EDIT # 1. Merci :) – Alix

+0

Et j'ai mis à jour la réponse pour vous. –

+0

Ah, parfait. +1, et je marque cette réponse et Akash comme les réponses. Merci beaucoup :) – Alix

1

Avez-vous pensé à utiliser un proxy dynamique pour intercepter tous les appels vers les classes cibles, faites tout ce que vous devez faire, puis transférer l'appel à la cible?

Quelque chose comme Linfu peut faire l'affaire: http://www.codeproject.com/KB/cs/LinFuPart1.aspx

+0

J'ai beaucoup regardé dans LinFu et d'autres frameworks AOP, ainsi que des choses très puissantes comme l'API Profiling. Je voulais vraiment l'utiliser, mais pour des raisons qui prendraient beaucoup de temps à expliquer, je ne peux pas les utiliser. Merci pour la suggestion cependant :) – Alix

1

code suivant suports Refractoring, Aucun code supplémentaire pour écrire, mais il ne peut pas exécuter très vite à cause de la réflexion, mais il va certainement vous donner accès à ce que vous voulez .

Vous devez jouer avec l'arbre d'expression un peu comme suivre,

public class MagicStringsTest 
{ 
    public static void Main(string[] args) 
    { 
     SemiNiceClass semiNice = new SemiNiceClass(); 
     // The user wants to do: semiNice.notSoNice.y = 50; 

     semiNice.Write(t=>t.y , 50); 

     Console.ReadLine(); 
    } 
} 

public class SemiNiceClass 
{ 

    public NotSoNiceClass notSoNice { get; set; } 
    public int z { get; set; } 

    public SemiNiceClass() 
    { 
     notSoNice = new NotSoNiceClass(); 
    } 

    public void Write<R>(Expression<Func<NotSoNiceClass,R>> exp, R value) 
    { 
     if (exp.Body.NodeType == ExpressionType.MemberAccess) 
     { 
      MemberExpression e = exp.Body as MemberExpression; 
      Console.WriteLine("Writing value for " + e.Member.Name 
       + " of NotSoNiceClass"); 
      FieldInfo info = e.Member as FieldInfo; 

      // value is set using reflection... 
      info.SetValue(notSoNice, value); 
     } 
     else 
     { 
      // throw exception, expecting of type x=>x.y 
     } 
    } 


} 
+0

Merci :) Je vous ai donné +1 mais je peux seulement marquer 1 réponse comme * la * réponse et j'ai choisi Hightechrider parce qu'il était celui qui suggérait Expressions en premier lieu. Je voudrais pouvoir marquer les deux:/ – Alix

+0

C'est ok, merci. –

Questions connexes