2010-09-21 18 views
15

Así que tienen un atributo personalizado, vamos a llamarlo MyCustomAttribute, que tiene un constructor de este modo:int anulable en Atributo Constructor o propiedad

public MyCustomAttribute(int? i) 
{ 
    // Code goes here 
} 

y declara una propiedad:

public int? DefaultProperty { get; set; } 

Ahora bien, si Quería usar la propiedad, necesitaría pasar un int o null, bueno, eso es lo que esperaría.

Pero esto da un error del compilador:

[MyCustomAttribute(1, DefaultProperty = 1)] 
public int? MyProperty { get; set; } 

y lo mismo ocurre esto:

[MyCustomAttribute(null,DefaultProperty = null)] 
public int? MyProperty { get; set; } 

El error es: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type tanto para el constructor y la propiedad.

¿Por qué es esto? Si cambio el constructor para tomar un int, me puede pasar en 0, pero no null, qué tipo de derrota el propósito del valor (que a veces puede ser nulo)

+1

¿Dónde está la enumeración que menciona en el título? (A nullable int no es una enumeración.) – dtb

+2

Puede proporcionar dos constructores: uno con un parámetro int y otro sin ningún parámetro. – dtb

+0

Sí, lo siento, alum fue al principio, resulta que es el nullable int – PostMan

Respuesta

19

La razón es que mientras tanto 0 y null son constantes, la expresión real necesaria para inicializar el parámetro constructor no lo es. Ambos requieren una conversión para ser una expresión válida int?. Bajo el capó que, esencialmente, genera la siguiente

[MyCustomAttribute(new Nullable<int>(0))] 

Esta expresión no ser constante no es legal como argumento atributo

EDITAR

DTB preguntó si esto es ilegal en valores de atributos por qué es legal para tener un parámetro predeterminado para un parámetro que admite nulos?

void Example(int? x = null); 

Lo que debe tener en cuenta es quién/qué está interpretando los valores.

para los atributos argumentos el valor se interpreta por el CLR. Las reglas relativas a los valores de atributos legales en realidad no han cambiado desde 1.0. Nullable no existía, por lo tanto, el CLR no entiende los patrones de inicialización que aceptan nulos.

Para los argumentos por defecto el vaule es interpretado por el compilador en el sitio de la llamada del método. Los compiladores entienden los valores que aceptan nulos y tienen un poco más de espacio para trabajar, ya que pueden crear expresiones no constantes en el sitio de llamadas en función de los valores en IL.

¿Cómo funcionan realmente sin embargo? En primer lugar el compilador en realidad codificar constantes y null diferente en IL

// x = 0 
[DefaultParameterValue(0)] 
// x = null 
[DefaultParameterValue(null)] 

En el lugar de llamar al compilador examina estos valores y crea la expresión apropiada (no constante) para el valor del parámetro.

+0

¿Pero por qué puedes tener 'int'? x = null 'en una firma de método? – dtb

+1

@dtb la razón por la cual los valores del argumento predeterminado son evaluados por el compilador y los argumentos del atributo por el CLR. El compilador C# (y Vb.Net) tiene una lógica especial para hacer que los valores de los argumentos predeterminados funcionen para los elementos nulables pero el CLR no. – JaredPar

+0

¿Pero no es 'int'? x = 0 'compilado a [DefaultParameterValue (new Nullable (0))]? – dtb

4

La regla de oro para el uso de atributos es que el tipo de parámetros que se toman para afirmar, con const palabra clave - es decir,todos los tipos elementales (int, char, etc.) y string. Sin new palabra clave puede ser utilizado en el atributo lista

No puede utilizar un atributo como esto parámetros:

[Custom(new object())] 
class Class { 
} 

ni aun como este (incluso cuando DateTime es un tipo de valor):

[Custom(new DateTime(2001,1,1))] 
class Class { 
} 

Por lo tanto, tampoco se permite usar Nullable<T>, porque pasar nulo o 1 es equivalente a hacer esto:

[Custom(new Nullable<int>())] 
//[Custom(null)] 
class Class { 
} 
[Custom(new Nullable<int>(1))] 
//[Custom(1)] 
class Class { 
} 
1

Sé que esta es una pregunta antigua, pero nadie ha respondido realmente por qué Nullable <> no está permitido. Esta respuesta concluyente a esto radica en the documentation for compiler error CS0655:

página

Positional and named parameters for an attribute class are limited to the following attribute parameter types:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, or ushort.
  • The type object.
  • The type System.Type.
  • An enum type, provided that it has public accessibility, and that any types in which it is nested also have public accessibility.
  • Single-dimensional arrays of the preceding types.

Esta documentación es para Visual Studio 2008, pero no he oído hablar de cualquier cambio reciente en esta área.

+0

Esto no responde por qué. – frenchone

+0

@frenchone Creo que responde por qué el código OP no compila, pero no responde el siguiente nivel de "por qué", que es "por qué los compiladores tomaron esta decisión cuando escribieron la especificación". – bart

10

Sé que la pregunta es por qué, pero para los desarrolladores que se redirigen a esta página y están buscando una respuesta. Esta es mi solución.

public class MyCustomAttribute: Attribute 
{ 
    private Nullable<bool> myBoolean = null; 

    public bool MyBooleanProperty { get { return this.myBoolean.GetValueOrDefault(); } set { this.myBoolean = value; } } 
    public bool IsMyBooleanPropertySpecified { get { return this.myBoolean != null; } } 
} 
+0

Es mejor utilizar el método GetValueOrDefault() (http://msdn.microsoft.com/en-us/library/72cec0e0(v=vs.110).aspx) en el getter en lugar de .Value; porque si myBoolean es nulo .Value arrojaría una excepción. –

+0

Sí, eso es una buena adición al código. ¡Thnx! –

2

Creo que sería fácil olvidarse de comprobar IsPropertyXSet antes de acceder a la variable. Creo que usar un nulo es mejor. Así que aquí es una posible manera de estructurar de que mientras se mantiene la anulable:

public class FooAttribute : Attribute { 
     public bool? SomeFlag { get; set; } 

     public bool SetSomeFlag { 
     get { 
      throw new Exception("should not be called"); // required for property visibility 
     } 
     set { 
      SomeFlag = value; 
     } 
    } 
} 

[Foo(SetSomeFlag=true)] 
public class Person { 
} 

[Foo] 
public class Person2 { // SetSomeFlag is not set 
} 

bool? b1 = ((FooAttribute)typeof(Person).GetCustomAttributes(typeof(FooAttribute), false)[0]).SomeFlag; // b1 = true 
bool? b2 = ((FooAttribute)typeof(Person2).GetCustomAttributes(typeof(FooAttribute), false)[0]).SomeFlag; // b2 = null 
0

Un hombre compra un Ferrari, pero algunos tonto establece el gobernador (el aparato que limita la velocidad del coche) a 30 mph. El tipo no puede cambiar al gobernador por algo más razonable, así que lo arranca. Ahora el Ferrari puede ir tan rápido como pueda el motor.

Microsoft no le permite usar elementos nulables como propiedades de atributos personalizados. Sin embargo, Microsoft le permite usar cadenas, que pueden ser nulas, como propiedades de atributos personalizados. Así que destruye la limitación por completo. Reemplace su int nullable con una cadena. Claro, una cadena es incluso menos restrictiva que una int anulable, ya que puede tener valores no enteros como "bob", pero ese es el precio que paga por Microsoft atornillando atributos personalizados, una característica de idioma, para un detalle de implementación en la CLR que debería ser irrelevante

Aquí está mi ejemplo.

public abstract class Contract : Attribute, IContract 
{ 
    public abstract void Check (Object root, String path, Object valueAtPath); 
} 

public sealed class DecimalPlacesContract : Contract 
{ 
    public String MinimumMantissaCount 
    { 
     get 
     { 
      return minimumMantissaCount?.ToString(); 
     } 

     set 
     { 
      minimumMantissaCount = value == null ? (int?) null : Int32.Parse(value); 
     } 
    } 

    public String MaximumMantissaCount 
    { 
     get 
     { 
      return maximumMantissaCount?.ToString(); 
     } 

     set 
     { 
      maximumMantissaCount = value == null ? (int?) null : Int32.Parse(value); 
     } 
    } 

    public String MinimumSignificantDigitCount 
    { 
     get 
     { 
      return minimumSignificantDigitCount?.ToString(); 
     } 

     set 
     { 
      minimumSignificantDigitCount = value == null ? (int?) null : Int32.Parse(value); 
     } 
    } 

    public String MaximumSignificantDigitCount 
    { 
     get 
     { 
      return maximumSignificantDigitCount?.ToString(); 
     } 

     set 
     { 
      maximumSignificantDigitCount = value == null ? (int?) null : Int32.Parse(value); 
     } 
    } 

    private int? minimumMantissaCount; 
    private int? maximumMantissaCount; 
    private int? minimumSignificantDigitCount; 
    private int? maximumSignificantDigitCount; 



    public override void Check (Object root, String path, Object valueAtPath) 
    { 
     decimal? value = valueAtPath as decimal?; 

     int mantissaCount = DecimalMisc.GetMantissaDigitCount(value ?? 0); 
     int significantDigitCount = DecimalMisc.GetSignificantDigitCount(value ?? 0); 

     if (value == null || 
      mantissaCount < minimumMantissaCount || 
      mantissaCount > maximumMantissaCount || 
      significantDigitCount < minimumSignificantDigitCount || 
      significantDigitCount > maximumSignificantDigitCount) 
     { 
      throw new ContractException(this, root, path, valueAtPath); 
     } 
    } 
} 

A continuación, se indica cómo utilizar las propiedades de los atributos personalizados.

private class Dollar 
    { 
     [DecimalPlacesContract (MinimumMantissaCount = "0", MaximumMantissaCount = "2")] 
     public decimal Amount { get; set; } 
    } 

    private class DollarProper 
    { 
     [DecimalPlacesContract (MinimumSignificantDigitCount = "2", MaximumSignificantDigitCount = "2")] 
     public decimal Amount { get; set; } 
    } 

Simplemente agregue comillas alrededor de los valores. Las propiedades no especificadas por defecto son nulas.

Cualquier valor incorrecto como "bob" hará que se emita una FormatException cuando llame GetCustomAttributes o GetCustomAttribute fuera de la instancia Type o PropertyInfo. Claro, sería bueno tener la verificación en tiempo de compilación de la int nullable, pero esto es suficiente. Así que rompe ese gobernador enseguida.

Cuestiones relacionadas