c# reflection type-conversion

c# - ¿Por qué Nullable<T> es tratado como especial por PropertyInfo.SetValue?



reflection type-conversion (2)

Al implementar una estructura similar a Nullable<T> descubrí que PropertyInfo.SetValue trata el tipo Nullable forma diferente a los demás. Para propiedad Nullable, puede establecer el valor del tipo subyacente

foo.GetType().GetProperty("NullableBool").SetValue(foo, true);

pero para el tipo personalizado arroja

System.ArgumentException: el objeto de tipo ''SomeType'' no se puede convertir a tipo NullableCase.CopyOfNullable 1 [SomeType]

incluso si todos los operadores de conversión son anulados de la misma manera que en Nullable<T>

Código para reproducir:

using System; namespace NullableCase { /// <summary> /// Copy of Nullable from .Net source code /// without unrelated methodts for brevity /// </summary> public struct CopyOfNullable<T> where T : struct { private bool hasValue; internal T value; public CopyOfNullable(T value) { this.value = value; this.hasValue = true; } public bool HasValue { get { return hasValue; } } public T Value { get { if (!hasValue) { throw new InvalidOperationException(); } return value; } } public static implicit operator CopyOfNullable<T>(T value) { return new CopyOfNullable<T>(value); } public static explicit operator T(CopyOfNullable<T> value) { return value.Value; } } class Foo { public Nullable<bool> NullableBool { get; set; } public CopyOfNullable<bool> CopyOfNullablBool { get; set; } } class Program { static void Main(string[] args) { Foo foo = new Foo(); foo.GetType().GetProperty("NullableBool").SetValue(foo, true); foo.GetType().GetProperty("CopyOfNullablBool").SetValue(foo, true); //here we get ArgumentException } } }

¿Por qué PropertyInfo.SetValue falla para el tipo CopyOfNullable y pasa para Nullable<T> ?


Cuando llama a .setValue (), el árbol de llamadas es el siguiente:

  • System.Reflection.RuntimePropertyInfo.SetValue (Object obj, Object value, Object [] index)
  • System.Reflection.RuntimePropertyInfo.SetValue (Object obj, Object value, BindingFlags invokeAttr, Binder Binder, Object [] index, CultureInfo culture)
  • System.Reflection.RuntimeMethodInfo.Invoke (Object obj, BindingFlags invokeAttr, Binder Binder, Object [] parámetros, CultureInfo cultura)
  • System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck (Object obj, BindingFlags invokeAttr, Binder Binder, Object [] parámetros, CultureInfo cultura)
  • System.Reflection.MethodBase.CheckArguments (Object [] parámetros, Binder Binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
  • System.RuntimeType. CheckValue (valor del objeto, carpeta del cuaderno, cultura CultureInfo, BindingFlags invokeAttr)
  • System.RuntimeType.TryChangeType (Valor del objeto, Binder Binder, CultureInfo culture, Boolean needsSpecialCast) [Esto solo recibe una llamada cuando usa su tipo personalizado]

Desafortunadamente, cuando el árbol de llamadas llega a RuntimeType.CheckValue comprueba si el objeto es una instancia del tipo (en este caso, Bool).

RuntimeType runtimeType; if (this.IsInstanceOfType(value)) { Type type = null; RealProxy realProxy = RemotingServices.GetRealProxy(value); type = (realProxy == null ? value.GetType() : realProxy.GetProxiedType()); if (type == this || !RuntimeTypeHandle.IsValueType(this)) { return value; } return RuntimeType.AllocateValueType(this, value, true); } if (!base.IsByRef) { if (value == null) { return value; } if (this == RuntimeType.s_typedRef) { return value; } }

Un Nullable pasará la verificación lógica ya que IsInstanceOfType(value) devuelve true, y el framework llamará a RemotingServices.GetRealProxy lo que permitirá que el método determine la igualdad en función de los valores de tipo genéricos.

Como sabemos, los tipos anulables son especiales y tienen soporte de idiomas adicional (piense en cómo puede usar int? lugar de Nullable<int> ). Cuando su tipo personalizado atraviesa esta comprobación de igualdad, no se considerará como una instancia igual y en su lugar continuará hacia el árbol de lógica, tratándolo como un tipo separado e invocando System.RuntimeType.TryChangeType

Si profundizamos en el código fuente en IsInstanceOfType , lo que encontramos es que RuntimeTypeHandle.CanCastTo se usa para determinar la igualdad, y delega el tipado en la máquina virtual (en este caso, los tipos anulables se preparan en la máquina virtual basada en la versión, como Anulable en el marco está decorado como [System.Runtime.Versioning.NonVersionable] )

// For runtime type, let the VM decide. if (fromType != null) { // both this and c (or their underlying system types) are runtime types return RuntimeTypeHandle.CanCastTo(fromType, this); }

Con suerte, lo que esto te dice es que los tipos Nullable tienen un soporte especial en el marco que no se puede replicar. Dado que Reflection utiliza este soporte, no podrá duplicar algunos de los matices de Nullable<T>


Nullable<T> tiene soporte especial dentro del sistema de tipo CLR para convertir automáticamente de T

De hecho, es imposible tener una instancia Nullable<T> de Nullable<T> ; cuadro de valores anulables para el valor subyacente o un nulo real.

Este es uno de los pocos tipos mágicos en el BCL; es imposible de duplicar