¿Cómo puedo acceder a una clase interna desde un ensamblado externo?

97

Tener un ensamblado que no puedo modificar (proporcionado por el proveedor) que tiene un método que devuelve un tipo de objeto pero que en realidad es de tipo interno.

¿Cómo puedo acceder a los campos y / o métodos del objeto desde mi ensamblaje?

Tenga en cuenta que no puedo modificar el ensamblaje proporcionado por el proveedor.

En esencia, esto es lo que tengo:

Del proveedor:

internal class InternalClass
  public string test;
end class

public class Vendor
  private InternalClass _internal;
  public object Tag {get{return _internal;}}
end class

De mi ensamblaje usando el ensamblaje del proveedor.

public class MyClass
{
  public void AccessTest()
  {
    Vendor vendor = new Vendor();
    object value = vendor.Tag;
    // Here I want to access InternalClass.test
  }
}
Stécy
fuente

Respuestas:

83

Sin acceso al tipo (y sin "InternalsVisibleTo", etc.) tendría que usar la reflexión. Pero una mejor pregunta sería: ¿ debería acceder a estos datos? No es parte del contrato de tipo público ... me parece que está destinado a ser tratado como un objeto opaco (para sus propósitos, no para el tuyo).

Lo ha descrito como un campo de instancia pública; para obtener esto a través de la reflexión:

object obj = ...
string value = (string)obj.GetType().GetField("test").GetValue(obj);

Si en realidad es una propiedad (no un campo):

string value = (string)obj.GetType().GetProperty("test").GetValue(obj,null);

Si no es público, deberá utilizar la BindingFlagssobrecarga de GetField/ GetProperty.

Aparte de importante : tenga cuidado con reflexiones como esta; la implementación podría cambiar en la próxima versión (rompiendo su código), o podría estar ofuscada (rompiendo su código), o podría no tener suficiente "confianza" (rompiendo su código). ¿Estás detectando el patrón?

Marc Gravell
fuente
Marc Me pregunto ... es posible acceder a campos / propiedades privados, pero ¿hay alguna manera de convertir el objeto devuelto por GetValue usando el tipo correcto?
codingadventures
1
@GiovanniCampo si sabes estáticamente cuál es el tipo: claro, solo envía. Si no conoce el tipo de forma estática, no está claro qué significaría esto
Marc Gravell
¿Qué pasa con las personas que publican cosas como internas que realmente son parte de la API pública? ¿O usaron InternalsVisibleTopero no incluyeron su ensamblaje? Si el símbolo no está realmente oculto, es parte del ABI.
binki
208

Solo veo un caso en el que permitiría exponer a sus miembros internos a otra asamblea y es para fines de prueba.

Diciendo que hay una manera de permitir que los ensamblajes "amigos" accedan a los componentes internos:

En el archivo AssemblyInfo.cs del proyecto, agrega una línea para cada ensamblaje.

[assembly: InternalsVisibleTo("name of assembly here")]

esta información está disponible aquí.

Espero que esto ayude.

zonkflut
fuente
Ampliando el caso de los propósitos de prueba. Cualquier escenario donde haya acoplado internos en diferentes contextos. Por ejemplo, en Unity, puede tener un ensamblado en tiempo de ejecución, un ensamblado de prueba y un ensamblado de editor. Los componentes internos del tiempo de ejecución deben estar visibles para los ensamblajes de prueba y editor.
Steve Buzonas
3
No creo que esta respuesta ayude: el OP dice que no puede modificar el ensamblaje del proveedor, y esta respuesta habla sobre la modificación del archivo AssemblyInfo.cs del proyecto del proveedor
Simon Green
6

Me gustaría argumentar un punto, que no se puede aumentar el ensamblaje original, usando Mono.Cecil puede inyectar [InternalsVisibleTo(...)]en el ensamblaje de 3pty. Tenga en cuenta que puede haber implicaciones legales: está jugando con el ensamblaje de 3pty y las implicaciones técnicas, si el ensamblado tiene un nombre fuerte, debe eliminarlo o volver a firmarlo con una clave diferente.

 Install-Package Mono.Cecil

Y el código como:

static readonly string[] s_toInject = {
  // alternatively "MyAssembly, PublicKey=0024000004800000... etc."
  "MyAssembly"
};

static void Main(string[] args) {
  const string THIRD_PARTY_ASSEMBLY_PATH = @"c:\folder\ThirdPartyAssembly.dll";

   var parameters = new ReaderParameters();
   var asm = ModuleDefinition.ReadModule(INPUT_PATH, parameters);
   foreach (var toInject in s_toInject) {
     var ca = new CustomAttribute(
       asm.Import(typeof(InternalsVisibleToAttribute).GetConstructor(new[] {
                      typeof(string)})));
     ca.ConstructorArguments.Add(new CustomAttributeArgument(asm.TypeSystem.String, toInject));
     asm.Assembly.CustomAttributes.Add(ca);
   }
   asm.Write(@"c:\folder-modified\ThirdPartyAssembly.dll");
   // note if the assembly is strongly-signed you need to resign it like
   // asm.Write(@"c:\folder-modified\ThirdPartyAssembly.dll", new WriterParameters {
   //   StrongNameKeyPair = new StrongNameKeyPair(File.ReadAllBytes(@"c:\MyKey.snk"))
   // });
}
Ondrej Svejdar
fuente
1
Para mí, no se puede modificar significa que proviene de un nuget y no quiero tener que crear y administrar un nuget local con las modificaciones. Además, para algunas personas, la pérdida de un nombre fuerte será importante. Pero este es un punto interesante.
binki
3

Reflexión.

using System.Reflection;

Vendor vendor = new Vendor();
object tag = vendor.Tag;

Type tagt = tag.GetType();
FieldInfo field = tagt.GetField("test");

string value = field.GetValue(tag);

Usa el poder sabiamente. No olvide comprobar los errores. :)

Colin Burnett
fuente
-2

Bueno, no puedes. Las clases internas no pueden ser visibles fuera de su ensamblaje, por lo que no hay forma explícita de acceder a ellas directamente, por supuesto, AFAIK. La única forma es utilizar el enlace tardío en tiempo de ejecución a través de la reflexión, luego puede invocar métodos y propiedades de la clase interna indirectamente.

Galilyou
fuente
5
Puedes llamar a cualquier cosa con reflexión
MrHinsh - Martin Hinshelwood