Come posso accedere a una classe interna da un assembly esterno?


97

Avere un assembly che non posso modificare (fornito dal fornitore) che ha un metodo che restituisce un tipo di oggetto ma è in realtà di un tipo interno.

Come posso accedere ai campi e / o ai metodi dell'oggetto dal mio assembly?

Tieni presente che non posso modificare l'assieme fornito dal fornitore.

In sostanza, ecco cosa ho:

Dal venditore:

internal class InternalClass
  public string test;
end class

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

Dalla mia assemblea utilizzando l'assembly del fornitore.

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

Risposte:


83

Senza l'accesso al tipo (e nessun "InternalsVisibleTo" ecc.) Dovresti usare la reflection. Ma una domanda migliore sarebbe: dovresti accedere a questi dati? Non fa parte del contratto di tipo pubblico ... mi sembra che sia inteso per essere trattato come un oggetto opaco (per i loro scopi, non per i tuoi).

Lo hai descritto come un campo di istanza pubblico; per ottenere questo tramite la riflessione:

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

Se è effettivamente una proprietà (non un campo):

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

Se non è pubblico, dovrai usare l' BindingFlagsoverload di GetField/ GetProperty.

Importante a parte : fai attenzione a riflessioni come questa; l'implementazione potrebbe cambiare nella versione successiva (interrompendo il codice), o potrebbe essere offuscata (interrompendo il codice), oppure potresti non avere abbastanza "fiducia" (rompere il tuo codice). Stai individuando lo schema?


Marc mi chiedo ... è possibile accedere a campi / proprietà privati ​​ma esiste un modo per eseguire il cast dell'oggetto restituito da GetValue utilizzando il tipo giusto?
codingadventures

1
@GiovanniCampo se sai staticamente di che tipo è: certo, cast. Se non conosci il tipo staticamente - non è chiaro cosa significhi
Marc Gravell

E le persone che pubblicano elementi come elementi interni che fanno davvero parte dell'API pubblica? Oppure hanno usato InternalsVisibleToma non hanno incluso il tuo assemblaggio? Se il simbolo non è veramente nascosto, fa parte dell'ABI.
binki

208

Vedo solo un caso in cui consenti l'esposizione dei tuoi membri interni a un'altra assemblea e questo è a scopo di test.

Dicendo che esiste un modo per consentire alle assemblee di "amici" di accedere agli interni:

Nel file AssemblyInfo.cs del progetto si aggiunge una riga per ogni assembly.

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

questa informazione è disponibile qui.

Spero che questo ti aiuti.


Ampliando il caso di scopi di test. Qualsiasi scenario in cui hai accoppiato interni in contesti diversi. Ad esempio, in Unity, potresti avere un assembly di runtime, un assembly di test e un assembly di editor. Gli interni di runtime dovrebbero essere visibili per testare e modificare gli assembly.
Steve Buzonas,

3
Non credo che questa risposta aiuti - l'OP dice che non può modificare l'assembly del fornitore e questa risposta parla della modifica del file AssemblyInfo.cs del progetto del fornitore
Simon Green

6

Vorrei sostenere un punto - che non è possibile aumentare l'assemblaggio originale - usando Mono.Cecil è possibile iniettare [InternalsVisibleTo(...)]all'assembly 3pty. Nota che potrebbero esserci implicazioni legali - stai scherzando con l'assembly 3pty e implicazioni tecniche - se l'assembly ha un nome sicuro devi o rimuoverlo o firmarlo nuovamente con una chiave diversa.

 Install-Package Mono.Cecil

E il codice come:

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
Per me, non può essere modificato significa che proviene da un nuget e non voglio dover creare e gestire un nuget locale con le modifiche. Inoltre, per alcune persone, la perdita di un nome forte sarà importante. Ma questo è un punto interessante.
binki

3

Riflessione.

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 il potere con saggezza. Non dimenticare il controllo degli errori. :)


-2

Beh, non puoi. Le classi interne non possono essere visibili al di fuori del loro assembly, quindi nessun modo esplicito per accedervi direttamente -AFAIK ovviamente. L'unico modo è usare l'associazione tardiva del runtime tramite reflection, quindi è possibile richiamare indirettamente metodi e proprietà dalla classe interna.


5
Puoi chiamare qualsiasi cosa con la riflessione
MrHinsh - Martin Hinshelwood
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.