Como posso acessar uma classe interna de um assembly externo?


97

Ter um assembly que não posso modificar (fornecido pelo fornecedor) que tem um método que retorna um tipo de objeto, mas é realmente de um tipo interno.

Como posso acessar os campos e / ou métodos do objeto da minha montagem?

Lembre-se de que não posso modificar a montagem fornecida pelo fornecedor.

Em essência, aqui está o que tenho:

Do fornecedor:

internal class InternalClass
  public string test;
end class

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

Da minha montagem usando a montagem do fornecedor.

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

Respostas:


83

Sem acesso ao tipo (e sem "InternalsVisibleTo" etc), você teria que usar reflexão. Mas uma pergunta melhor seria: você deveria acessar esses dados? Não faz parte do contrato de tipo público ... parece-me que se destina a ser tratado como um objeto opaco (para os propósitos deles, não os seus).

Você o descreveu como um campo de instância pública; para obter isso por meio de reflexão:

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

Se for realmente uma propriedade (não um campo):

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

Se não for público, você precisará usar a BindingFlagssobrecarga de GetField/ GetProperty.

À parte importante : cuidado com reflexões como esta; a implementação pode mudar na próxima versão (quebrar seu código), ou pode ser ofuscada (quebrar seu código), ou você pode não ter "confiança" suficiente (quebrar seu código). Você está identificando o padrão?


Marc, eu me pergunto ... é possível acessar campos / propriedades privados, mas existe uma maneira de converter o objeto retornado por GetValue usando o tipo certo?
codingadventures de

1
@GiovanniCampo se você souber estaticamente qual é o tipo: claro - apenas lance. Se você não conhece o tipo estaticamente - não está claro o que isso significaria
Marc Gravell

E as pessoas que publicam coisas como internas que realmente fazem parte da API pública? Ou eles usaram, InternalsVisibleTomas não incluíram sua montagem? Se o símbolo não estiver realmente oculto, ele faz parte da ABI.
binki,

208

Vejo apenas um caso em que você permitiria a exposição de seus membros internos a outra assembléia e que é para fins de teste.

Dizendo que existe uma maneira de permitir o acesso de assemblies "Friend" aos internos:

No arquivo AssemblyInfo.cs do projeto, você adiciona uma linha para cada montagem.

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

esta informação está disponível aqui.

Espero que isto ajude.


Expandindo o caso de fins de teste. Qualquer cenário em que você tenha acoplado internos em diferentes contextos. Por exemplo, no Unity, você pode ter um assembly de tempo de execução, um assembly de teste e um assembly de editor. Os componentes internos do tempo de execução devem estar visíveis para os assemblies de teste e editor.
Steve Buzonas

3
Não acho que esta resposta ajude - o OP diz que ele não pode modificar o assembly do fornecedor, e esta resposta fala sobre como modificar o arquivo AssemblyInfo.cs do projeto do fornecedor
Simon Green,

6

Eu gostaria de argumentar um ponto - que você não pode aumentar o assembly original - usando Mono.Cecil você pode injetar [InternalsVisibleTo(...)]no assembly 3pty. Observe que pode haver implicações legais - você está mexendo com o assembly 3pty e implicações técnicas - se o assembly tiver um nome forte, você precisará removê-lo ou assiná-lo novamente com uma chave diferente.

 Install-Package Mono.Cecil

E o 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"))
   // });
}

1
Para mim, não pode ser modificado significa que ele vem de um nuget e não quero ter que criar e gerenciar um nuget local com as modificações. Além disso, para algumas pessoas, a perda de um nome forte é importante. Mas este é um ponto interessante.
binki,

3

Reflexão.

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);

Use o poder com sabedoria. Não se esqueça da verificação de erros. :)


-2

Bem, você não pode. Classes internas não podem ser visíveis fora de seu assembly, portanto, não há maneira explícita de acessá-lo diretamente - AFAIK, é claro. A única maneira é usar a ligação tardia de tempo de execução por meio de reflexão, então você pode invocar métodos e propriedades da classe interna indiretamente.


5
Você pode chamar qualquer coisa com reflexão
MrHinsh - Martin Hinshelwood
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.