2009-02-17 7 views
7

Así que tengo una clase PropertyBag que está destinada a implementar INotifyPropertyChanged. Para hacer que este código funcione lo más limpiamente posible y para evitar el error del usuario, estoy usando la pila para obtener el nombre de la propiedad. Vea, si el nombre de la propiedad no concuerda exactamente con la propiedad real, entonces tendrá una falla y estoy tratando de protegerme de eso.¿Alguna forma de evitar la optimización en línea de propiedades en C#?

lo tanto, aquí es un ejemplo de uso de la clase:

public class MyData : PropertyBag 
{ 
    public MyData() 
    { 
     Foo = -1; 
    } 

    public int Foo 
    { 
     get { return GetProperty<int>(); } 
     set { SetProperty(value); } 
    } 
} 

El código importante para el PropertyBag base está aquí:

public abstract class PropertyBag : INotifyPropertyChanged 
{ 
    protected T GetProperty<T>() 
    { 
     string propertyName = PropertyName((new StackTrace()).GetFrame(1)); 
     if (propertyName == null) 
      throw new ArgumentException("GetProperty must be called from a property"); 

     return GetValue<T>(propertyName); 
    } 

    protected void SetProperty<T>(T value) 
    { 
     string propertyName = PropertyName((new StackTrace()).GetFrame(1)); 
     if (propertyName == null) 
      throw new ArgumentException("SetProperty must be called from a property"); 

     SetValue(propertyName, value); 
    } 

    private static string PropertyName(StackFrame frame) 
    { 
     if (frame == null) return null; 
     if (!frame.GetMethod().Name.StartsWith("get_") && 
      !frame.GetMethod().Name.StartsWith("set_")) 
      return null; 

     return frame.GetMethod().Name.Substring(4); 
    } 
} 

Así que ahora que usted ha visto mi código, que pueda le dirá el problema ... En algunos casos, en la compilación de lanzamiento, el colocador "Foo" en el constructor "MyData" parece estar optimizado para alinearse como SetProperty (-1). Desafortunadamente, esta optimización en línea falla en mi método SetProperty porque ya no lo llamo desde una propiedad. FALLAR. Parece que no puedo confiar en StackTrace de esta manera.

¿Alguien puede A: averiguar una mejor manera de hacer esto, pero aún así evitar pasar en "Foo" a GetProperty y SetProperty?
B: ¿Encuentra una forma de decirle al compilador que no optimice en este caso?

+2

Olvidé decir que no utilizo un diccionario para el almacenamiento, ya que tiende a tener un pequeño número de propiedades, escanear una lista plana es en realidad mucho más rápido (la línea divisoria de aguas es de aproximadamente 120 teclas). –

+0

Puedes hacer que sean virtuales ... – BrainSlugs83

Respuesta

13

El uso de la pila aquí es lento e innecesario; Me basta con utilizar:

get { return GetProperty<int>("Foo"); } 
set { SetProperty("Foo", value); } 

(pista: He hecho un montón de trabajo con modelos de propiedad personalizada, sé que esto funciona bien ...)

Otra alternativa es una clave de objeto (el uso hacer referencia a la igualdad para comparar) - un montón de ComponentModel funciona de esta manera, al igual que algunas de las propiedades de WF/WPF:

static readonly object FooKey = new object(); 
... 
get { return GetProperty<int>(FooKey); } 
set { SetProperty(FooKey, value); } 

por supuesto, se podría declarar un tipo para las teclas (con una propiedad Name), y usa eso:

static readonly PropertyKey FooKey = new PropertyKey("Foo"); 

etc; Sin embargo, para responder a la pregunta: ¿marcarlo (pero no lo hacen hacer esto) con:

[MethodImpl(MethodImplOptions.NoInlining)] 

o

[MethodImpl(MethodImplOptions.NoOptimization)] 

o

[MethodImpl(MethodImplAttributes.NoOptimization 
    | MethodImplAttributes.NoInlining)] 

+0

Gracias por la respuesta. Ese atributo NoInlining es lo que me preguntaba ... pero estoy de acuerdo ... No lo usaré. Requiere que los usuarios sepan hacer eso, lo que es peor que usar "Foo". Respuesta impresionante por muchas razones. Gracias. –

+0

Para que quede claro, estaba usando este mecanismo para evitar problemas de refactorización en el futuro. Si utiliza una herramienta de refactorización para cambiar el nombre de la propiedad, el código fallará de repente, ya que el nombre debe ser el mismo que el de la propiedad. –

+0

Actualización: en C# 6 puede usar el método 'nameof (...)' para obtener el nombre de una propiedad. Esto evita problemas de refactorización en el futuro. –

1

Si desea evitar cadenas codificadas puede utilizar:

protected T GetProperty<T>(MethodBase getMethod) 
{ 
    if (!getMethod.Name.StartsWith("get_") 
    { 
     throw new ArgumentException(
      "GetProperty must be called from a property"); 
    } 
    return GetValue<T>(getMethod.Name.Substring(4)); 
} 

añadir más cordura comprobar como mejor le parezca

entonces la propiedad obtiene convierten

public int Foo 
{ 
    get { return GetProperty<int>(MethodInfo.GetCurrentMethod()); }  
} 

conjuntos cambian de la misma manera.

GetCurrentMethod() también atraviesa la pila pero lo hace a través de una llamada no administrada (interna) que depende de los marcadores de pila, por lo que también funcionará en el modo de lanzamiento.

Alternativamente para una solución rápida [MethodImpl] con MethodImplAttributes.NoOptimization) o MethodImplAttributes.NoInlining también funcionará aunque con un golpe de rendimiento (aunque dado que está atravesando el marco de pila cada vez que este golpe es insignificante).

Una técnica adicional, conseguir un cierto nivel de comprobación de tiempo de compilación es:

public class PropertyHelper<T> 
{ 
    public PropertyInfo GetPropertyValue<TValue>(
     Expression<Func<T, TValue>> selector) 
    { 
     Expression body = selector; 
     if (body is LambdaExpression) 
     { 
      body = ((LambdaExpression)body).Body; 
     } 
     switch (body.NodeType) 
     { 
      case ExpressionType.MemberAccess: 
       return GetValue<TValue>(
        ((PropertyInfo)((MemberExpression)body).Member).Name); 
      default: 
       throw new InvalidOperationException(); 
     } 
    } 
} 

private static readonly PropertyHelper<Xxxx> propertyHelper 
    = new PropertyHelper<Xxxx>(); 

public int Foo 
{ 
    get { return propertyHelper.GetPropertyValue(x => x.Foo); }  
} 

donde xxxxx es la clase en la que se define la propiedad. Si la naturaleza estática causa problemas (enhebrar o hacer que sea un valor de instancia también es posible).

Debo señalar que estas técnicas son realmente por el bien de los intereses, no estoy sugiriendo que sean buenas técnicas generales para su uso.

+0

Me gusta esto. Lo hace de modo que refactorizar el código no cause fallas. –

+0

No funcionará para las propiedades del setter - (comienzan con "set_") - y esto es más o menos lo que el OP ya estaba haciendo - pero el JITer le indica el método si tiene activadas las optimizaciones, por lo que este completamente ignora el problema del OP. – BrainSlugs83

2

Usar la pila no es una buena idea. Confía en la implementación interna del compilador para vincular artificialmente su bolsa de propiedades con las propiedades del idioma.

  1. teniendo el requisito de agregar el atributo MethodImpl hace que el uso de su bolsa de propiedad no sea transparente para otros desarrolladores.
  2. incluso si el bolso de la propiedad tiene el atributo MethodImpl, nada le garantiza que será el primer cuadro en la pila de llamadas. Es posible que el ensamblaje se haya instrumentado o modificado para inyectar llamadas entre la propiedad real y la llamada a su bolsa de propiedades. (Piense en la programación de aspecto)
  3. nuevos idiomas o incluso una futura versión del compilador de C# pueden decorar los descriptores de acceso de una manera diferente, entonces '_get' y
  4. La construcción de la pila de llamadas es una operación relativamente lenta, ya que requiere el comprimido interno pila para descomprimir y el nombre de cada tipo y método que se obtendrá utilizando reflexión.

Usted debe realmente sólo poner en práctica sus descriptores de acceso bolsa de propiedades para tomar un parámetro para identificar la propiedad - ya sea un nombre de cadena (como Hastable) o un objeto (como la bolsa propiedad de dependencia de WPF)

2

Prueba el nuevo atributo [CallerMemberName].

Colóquelo en un parámetro para su método ([CallerMemberName] callerName = null) y el compilador reescribirá todas las llamadas a su método para pasar el nombre de la persona que llama automáticamente (sus llamadas no pasan el parámetro).

No elimina ninguna optimización, y es mucho más rápido que lambdas o reflejo o pilas, y funciona en modo Release.

P.S. si CallerMemberNameAttribute no existe en su versión del marco, simplemente defínalo (vacío). Es una función de idioma, no una función de marco.Cuando el compilador ve [CallerMemberNameAttribute] en un parámetro, simplemente funciona.

+0

'CallerMemeberName' es impresionante, pero tenga en cuenta que requiere un compilador C# 5.0 (incluso si lo define usted mismo [por ejemplo, para apuntar a .NET Framework 4.0], pero está usando Visual Studio 2010, por ejemplo, lo hará no funciona; necesita un compilador que * conozca * sobre este atributo). – BrainSlugs83

+0

Solo quiero agregar la nueva palabra clave de idioma "nameof" es una solución aún mejor ya que nada sucede en tiempo de ejecución. –

Cuestiones relacionadas