Wie kann ich von einer externen Assembly aus auf eine interne Klasse zugreifen?

97

Eine Assembly, die ich nicht ändern kann (vom Hersteller bereitgestellt), deren Methode einen Objekttyp zurückgibt, die jedoch tatsächlich vom internen Typ ist.

Wie kann ich von meiner Assembly aus auf die Felder und / oder Methoden des Objekts zugreifen?

Beachten Sie, dass ich die vom Hersteller bereitgestellte Baugruppe nicht ändern kann.

Im Wesentlichen habe ich Folgendes:

Vom Anbieter:

internal class InternalClass
  public string test;
end class

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

Aus meiner Baugruppe mit der Lieferantenbaugruppe.

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

Antworten:

83

Ohne Zugriff auf den Typ (und ohne "InternalsVisibleTo" usw.) müssten Sie Reflection verwenden. Eine bessere Frage wäre jedoch: Sollten Sie auf diese Daten zugreifen? Es ist nicht Teil des öffentlichen Vertrags ... es klingt für mich so, als ob es als undurchsichtiges Objekt behandelt werden soll (für ihre Zwecke, nicht für Ihre).

Sie haben es als öffentliches Instanzfeld beschrieben. um dies durch Reflexion zu bekommen:

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

Wenn es sich tatsächlich um eine Eigenschaft handelt (kein Feld):

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

Wenn es nicht öffentlich ist, müssen Sie die BindingFlagsÜberladung von GetField/ verwenden GetProperty.

Wichtig beiseite : Seien Sie vorsichtig mit solchen Überlegungen; Die Implementierung könnte sich in der nächsten Version ändern (Code brechen) oder sie könnte verschleiert sein (Code brechen), oder Sie haben möglicherweise nicht genug "Vertrauen" (Code brechen). Erkennen Sie das Muster?

Marc Gravell
quelle
Marc Ich frage mich ... es ist möglich, auf private Felder / Eigenschaften zuzugreifen, aber gibt es eine Möglichkeit, das von GetValue zurückgegebene Objekt mit dem richtigen Typ umzuwandeln?
Codierung Abenteuer
1
@GiovanniCampo, wenn Sie statisch wissen, was der Typ ist: sicher - nur besetzen. Wenn Sie den Typ statisch nicht kennen - es ist unklar, was dies überhaupt bedeuten würde
Marc Gravell
Was ist mit Leuten, die Dinge als Interna veröffentlichen, die wirklich Teil der öffentlichen API sind? Oder haben sie InternalsVisibleToIhre Baugruppe verwendet , aber nicht eingeschlossen? Wenn das Symbol nicht wirklich verborgen ist, ist es Teil des ABI.
Binki
208

Ich sehe nur einen Fall, in dem Sie zulassen würden, dass Ihre internen Mitglieder einer anderen Versammlung ausgesetzt werden, und zwar zu Testzwecken.

Angenommen, es gibt eine Möglichkeit, "Friend" -Baugruppen Zugriff auf Interna zu gewähren:

In der Datei AssemblyInfo.cs des Projekts fügen Sie für jede Baugruppe eine Zeile hinzu.

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

Diese Informationen finden Sie hier.

Hoffe das hilft.

Zonkflut
quelle
Erweiterung des Falles von Testzwecken. Jedes Szenario, in dem Sie Interna in verschiedenen Kontexten gekoppelt haben. In Unity verfügen Sie möglicherweise über eine Laufzeitassembly, eine Testassembly und eine Editorassembly. Laufzeit-Interna sollten für Test- und Editor-Assemblys sichtbar sein.
Steve Buzonas
3
Ich glaube nicht, dass diese Antwort hilft - das OP sagt, dass er die Assembly des Anbieters nicht ändern kann, und diese Antwort spricht über das Ändern der AssemblyInfo.cs-Datei des Projekts des Anbieters
Simon Green
6

Ich möchte einen Punkt argumentieren - dass Sie die ursprüngliche Baugruppe nicht erweitern können - mit Mono.Cecil können Sie [InternalsVisibleTo(...)]die 3pty-Baugruppe injizieren . Beachten Sie, dass es rechtliche Auswirkungen geben kann - Sie haben Probleme mit der 3pty-Baugruppe und den technischen Auswirkungen -, wenn die Baugruppe einen starken Namen hat, müssen Sie sie entweder entfernen oder mit einem anderen Schlüssel neu signieren.

 Install-Package Mono.Cecil

Und der Code mag:

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
quelle
1
Nicht ändern bedeutet für mich, dass es von einem Nuget stammt und ich kein lokales Nuget mit den Änderungen erstellen und verwalten muss. Für manche Menschen ist auch der Verlust eines starken Namens von Bedeutung. Dies ist jedoch ein interessanter Punkt.
Binki
3

Reflexion.

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

Nutze die Kraft mit Bedacht. Vergessen Sie nicht die Fehlerprüfung. :) :)

Colin Burnett
quelle
-2

Das kannst du nicht. Interne Klassen können außerhalb ihrer Assembly nicht sichtbar sein, daher gibt es natürlich keine explizite Möglichkeit, direkt darauf zuzugreifen. Die einzige Möglichkeit besteht darin, die späte Laufzeitbindung über Reflektion zu verwenden. Anschließend können Sie indirekt Methoden und Eigenschaften aus der internen Klasse aufrufen.

Galilyou
quelle
5
Sie können alles mit Reflexion nennen
MrHinsh - Martin Hinshelwood