2010-04-29 8 views
5

Estoy escribiendo un sistema que subyace a las aplicaciones de programador y que necesita detectar su acceso a ciertos datos. sobre todo por lo que puedo hacer con propiedades, como esto:¿Una forma "elegante" de identificar un campo?

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

Entonces voy y ajustar el get y set descriptores de acceso para que manejan los accesos de manera apropiada. Sin embargo, esto requiere que los usuarios (programadores de aplicaciones) definan todos sus datos como propiedades.

Si los usuarios desean utilizar clases preexistentes que tienen campos "normales" (en lugar de propiedades), no puedo detectar esos accesos. Ejemplo:

public class NotSoNiceClass { 
    public int y; 
} 

no puedo detectar accesos a y. Sin embargo, quiero permitir el uso de clases preexistentes. Como compromiso los usuarios son responsables de notificarme cada vez que se produce un acceso a ese tipo de datos. Por ejemplo:

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

Algo así. Créanme, he investigado esto muy a fondo e incluso el análisis directo del bytecode para detectar accesos no es confiable debido a posibles indirecciones, etc. Realmente necesito que los usuarios me notifiquen.

Y ahora mi pregunta: ¿podría recomendarme una forma "elegante" de realizar estas notificaciones? (Sí, sé que toda esta situación no es "elegante" para empezar, estoy tratando de no empeorarla;)). ¿Como lo harias?

Este es un problema para mí, porque en realidad la situación es la siguiente: Tengo la clase siguiente:

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

Si el usuario quiere hacer esto:

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

Deben lugar hacer algo como esto:

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

Dónde Write volverá un clon de notSoNice, que es lo que quería que el accesorio set hiciera de todos modos. Sin embargo, usar una cadena es bastante feo: si más adelante refactorizan el campo, tendrán que pasar por sus accesos Write("notSoNice") y cambiar la cadena.

¿Cómo podemos identificar el campo? Solo puedo pensar en strings, ints y enums (es decir, ints nuevamente). Pero:

  • Ya hemos discutido el problema con las cadenas.
  • Los Ints son un dolor. Son aún peores porque el usuario necesita recordar qué int corresponde a cada campo. Refactorizar es igualmente difícil.
  • Enums (como NOT_SO_NICE y Z, es decir, los campos de SemiNiceClass) facilidad refactorización, pero requieren que el usuario escriba una enumeración por clase (SemiNiceClass, etc), con un valor por campo de la clase. Es molesto. No quiero que me odien;)

Entonces, ¿por qué, oí que preguntas, no podemos hacer esto (abajo)?

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

Porque necesito saber qué se está accediendo a campo y semiNice.notSoNice no identifica un campo. Es el valor del campo, no el campo en sí.

suspiro. Sé que esto es feo. Créanme;)

Agradeceré mucho las sugerencias.

¡Gracias de antemano!

(también, no podía subir con buenas etiquetas para esta pregunta Por favor, hágamelo saber si usted tiene mejores ideas, y voy a editarlos.)


editar # 1: Sugerencia de Hightechrider: Expresiones.

No se si Write(x =>semiNice.y, 0) estaba destinado a estar basado en las clases que escribí para mi pregunta (SemiNiceClass, etc.) o si solo es un ejemplo, pero si es el primero no coincide con la estructura: hay no y campo en SemiNiceClass. ¿Quería decir Write(x =>semiNice.notSoNice.y, 0)?

No estoy seguro de cómo se quiere decir para mí utilizar esto ... Voy a publicar el código que he escrito:

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; 
    } 

} 

¿Cómo consigo que la información en Write? No puedo encontrar ninguna forma de extraer esa información del Func<,>. Además, ¿por qué escribir semiNice.Write(x => semiNice.notSoNice.y, 50); en lugar de semiNice.Write(() => semiNice.notSoNice.y, 50);, ya que no estamos usando x para nada?

Gracias.


editar # 2: sugerencia de Hans Passant: la sustitución de campos con propiedades.

Esto es lo que originalmente tenía la intención de hacer, pero volver a compilar no es una opción.


editar # 3: la sugerencia de Ben Hoffstein: proxies dinámicos; LinFu.

Ya he investigado esto por mucho tiempo, y por razones relativamente complejas no puedo usarlo. Sería demasiado largo para explicarlo, pero puedes estar seguro: si pudiera usarlo, lo haría. Es mucho, mucho más limpio que mi solución actual.

Respuesta

2

Usa una expresión, p. Ej. Escribir (x => semiNice.y, 0)

Esta técnica se utiliza a menudo como una forma de evitar las cadenas mágicas.

p. Ej.

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

He actualizado mi pregunta. Ver EDIT # 1. Gracias :) – Alix

+0

Y actualicé la respuesta por ti. –

+0

Ah, perfecto. +1, y estoy marcando esta respuesta y Akash como las respuestas. Muchas gracias :) – Alix

1

Ha considerado el uso de un proxy dinámico para interceptar todas las llamadas a las clases de objetivos, hacer lo que sea que tiene que hacer, y luego transmita la llamada al destino?

Algo así como Linfu puede hacer el truco: http://www.codeproject.com/KB/cs/LinFuPart1.aspx

+0

He mirado mucho en Linfu y otros marcos de AOP, así como cosas muy potente, como la API de perfiles. Realmente quería usar esto, pero por razones que tardarían mucho en explicarlo, no puedo usarlas. Gracias por la sugerencia :) – Alix

1

siguiente código suports Refractoring, ningún código adicional para escribir, sin embargo, no puede ejecutar muy rápido debido a la reflexión, pero, sin duda le dará acceso a lo que quiere .

tienes que jugar con el árbol de expresión un poco como seguir,

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

Gracias :) Te di +1 pero solo puedo marcar 1 respuesta como * la * respuesta y he elegido Hightechrider porque fue él quien sugirió expresiones en primer lugar. Ojalá pudiera marcar ambos:/ – Alix

+0

Está bien, gracias. –

Cuestiones relacionadas