example - Tipo dinámico C#gotcha
remarks c# (2)
Me encontré con lo más extraño y estoy un poco loco = soplado en este momento ...
El siguiente programa compila bien, pero cuando lo ejecuta obtiene una RuntimeBinderException
cuando intenta leer Value
. ''object'' does not contain a definition for ''Value''
class Program
{
interface IContainer
{
int Value { get; }
}
class Factory
{
class Empty : IContainer
{
public int Value
{
get { return 0; }
}
}
static IContainer nullObj = new Empty();
public IContainer GetContainer()
{
return nullObj;
}
}
static void Main(string[] args)
{
dynamic factory = new Factory();
dynamic container = factory.GetContainer();
var num0 = container.Value; // WTF!? RuntimeBinderException, really?
}
}
Aquí está la parte alucinante. Mueva el tipo anidado Factory+Empty
fuera de la clase Factory
, así:
class Empty : IContainer
{
public int Value
{
get { return 0; }
}
}
class Factory...
Y el programa funciona bien, ¿a quién le importa explicar por qué?
EDITAR
En mi aventura de codificación, por supuesto, hice algo en lo que debería haber pensado primero. Es por eso que me ves divagando un poco acerca de la diferencia entre la clase privada e interna. Esto se debía a que había configurado InternalsVisibleToAttribute
que hizo que mi proyecto de prueba (que consumía los bits en este caso) se comportara de la manera en que lo hicieron, lo cual fue todo por diseño, aunque me aludió desde el principio.
Lea la respuesta de Eric Lippert para una buena explicación del resto.
Lo que realmente me llamó la atención fue que la carpeta dinámica toma en cuenta la visibilidad del tipo de instancia. Tengo mucha experiencia en JavaScript y como programador de JavaScript donde realmente no existe lo público ni lo privado, me engañó por completo el hecho de que la visibilidad importaba, es decir, después de todo, estaba accediendo a este miembro como si era del tipo de interfaz pública (pensé que la dinámica era simplemente azúcar sintáctica para la reflexión) pero la carpeta dinámica no puede hacer tal suposición a menos que le dé una pista, usando un molde simple.
El principio fundamental de "dinámico" en C # es: en tiempo de ejecución hacer el análisis de tipo de la expresión como si el tipo de tiempo de ejecución hubiera sido el tipo de tiempo de compilación . Entonces, veamos qué pasaría si realmente hiciéramos eso:
dynamic num0 = ((Program.Factory.Empty)container).Value;
Ese programa fallaría porque Empty
no es accesible. dynamic
no le permitirá hacer un análisis que hubiera sido ilegal en primer lugar.
Sin embargo, el analizador de tiempo de ejecución se da cuenta de esto y decide hacer trampa un poco. Se pregunta "¿hay una clase base de Empty que sea accesible?" y la respuesta es obviamente sí. Entonces decide recurrir a la clase base y analiza:
dynamic num0 = ((System.Object)container).Value;
Lo cual falla porque ese programa le daría un error "el objeto no tiene un miembro llamado Valor". ¿Cuál es el error que está recibiendo?
El análisis dinámico nunca dice "oh, debes haber querido decir"
dynamic num0 = ((Program.IContainer)container).Value;
porque, por supuesto, si eso es lo que querías decir, eso es lo que habrías escrito en primer lugar . Una vez más, el propósito de la dynamic
es responder a la pregunta de qué hubiera pasado si el compilador hubiera conocido el tipo de tiempo de ejecución , y el envío a una interfaz no le proporciona el tipo de tiempo de ejecución.
Cuando te mueves Empty
afuera, entonces el analizador de tiempo de ejecución dinámico finge que escribiste:
dynamic num0 = ((Empty)container).Value;
Y ahora se puede acceder a Empty
y el elenco es legal, por lo que obtienes el resultado esperado.
ACTUALIZAR:
puede compilar ese código en un ensamblado, hacer referencia a este ensamblaje y funcionará si el tipo Vacío está fuera de la clase, lo que lo haría interno por defecto
No puedo reproducir el comportamiento descrito. Probemos un pequeño ejemplo:
public class Factory
{
public static Thing Create()
{
return new InternalThing();
}
}
public abstract class Thing {}
internal class InternalThing : Thing
{
public int Value {get; set;}
}
> csc /t:library bar.cs
class P
{
static void Main ()
{
System.Console.WriteLine(((dynamic)(Factory.Create())).Value);
}
}
> csc foo.cs /r:bar.dll
> foo
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
''Thing'' does not contain a definition for ''Value''
Y verá cómo funciona esto: el encuadernador de tiempo de ejecución ha detectado que InternalThing
es interno para el ensamblaje foráneo y, por lo tanto, no se puede acceder en foo.exe. Por lo tanto, vuelve al tipo de base pública, Thing
, que es accesible pero no tiene la propiedad necesaria.
No puedo reproducir el comportamiento que describes, y si puedes reproducirlo, has encontrado un error. Si tiene una pequeña reproducción del error, me complace pasarla a mis antiguos colegas.
Supongo que, en tiempo de ejecución, las llamadas al método contenedor se resuelven en la clase privada Empty, lo que hace que el código falle. Hasta donde yo sé, dynamic no se puede usar para acceder a miembros privados (o miembros públicos de una clase privada)
Esto debería (por supuesto) funcionar:
var num0 = ((IContainer)container).Value;
Aquí, es clase vacía que es privada: por lo tanto, no puede manipular instancias vacías fuera de la clase declarante (fábrica). Es por eso que tu código falla.
Si Empty fuera interno, podría manipular sus instancias en todo el ensamblaje (bueno, no porque Factory sea privado), permitiendo que todas las llamadas dinámicas sean permitidas y su código funcione.