Comment puis-je accéder à une classe interne à partir d'un assembly externe?

97

Avoir un assembly que je ne peux pas modifier (fourni par le fournisseur) qui a une méthode retournant un type d' objet mais qui est vraiment de type interne.

Comment accéder aux champs et / ou méthodes de l'objet depuis mon assemblage?

Gardez à l'esprit que je ne peux pas modifier l'assemblage fourni par le fournisseur.

En substance, voici ce que j'ai:

Du fournisseur:

internal class InternalClass
  public string test;
end class

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

De mon assemblage en utilisant l'assemblage du fournisseur.

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

Réponses:

83

Sans accès au type (et sans "InternalsVisibleTo", etc.), vous devriez utiliser la réflexion. Mais une meilleure question serait: devriez- vous accéder à ces données? Cela ne fait pas partie du contrat de type public ... il me semble qu'il est destiné à être traité comme un objet opaque (pour leurs fins, pas pour les vôtres).

Vous l'avez décrit comme un champ d'instance publique; pour obtenir cela par réflexion:

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

S'il s'agit en fait d'une propriété (pas d'un champ):

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

S'il n'est pas public, vous devrez utiliser la BindingFlagssurcharge de GetField/ GetProperty.

A part important : soyez prudent avec une réflexion comme celle-ci; l'implémentation pourrait changer dans la prochaine version (casser votre code), ou elle pourrait être obscurcie (casser votre code), ou vous pourriez ne pas avoir assez de "confiance" (casser votre code). Êtes-vous en train de repérer le motif?

Marc Gravell
la source
Marc Je me demande ... il est possible d'accéder aux champs / propriétés privés mais y a-t-il un moyen de convertir l'objet retourné par GetValue en utilisant le bon type?
codingadventures
1
@GiovanniCampo si vous savez statiquement quel est le type: bien sûr - il suffit de lancer. Si vous ne connaissez pas le type statiquement - on ne sait pas ce que cela signifierait
Marc Gravell
Qu'en est-il des personnes qui publient des éléments en tant que composants internes qui font vraiment partie de l'API publique? Ou ils ont utilisé InternalsVisibleTomais n'ont pas inclus votre assemblage? Si le symbole n'est pas vraiment caché, il fait partie de l'ABI.
binki
208

Je ne vois qu'un seul cas dans lequel vous autoriseriez l'exposition de vos membres internes à une autre assemblée et c'est à des fins de test.

Dire qu'il existe un moyen d'autoriser les assemblys "Friend" à accéder aux composants internes:

Dans le fichier AssemblyInfo.cs du projet, vous ajoutez une ligne pour chaque assembly.

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

cette information est disponible ici.

J'espère que cela t'aides.

zonkflut
la source
Expansion sur le cas des fins de test. Tout scénario dans lequel vous avez couplé des composants internes dans différents contextes. Par exemple, dans Unity, vous pouvez avoir un assemblage d'exécution, un assemblage de test et un assemblage d'éditeur. Les composants internes d'exécution doivent être visibles pour les assemblys de test et d'éditeur.
Steve Buzonas
3
Je ne pense pas que cette réponse aide - l'OP dit qu'il ne peut pas modifier l'assemblage du fournisseur, et cette réponse parle de la modification du fichier AssemblyInfo.cs du projet du fournisseur
Simon Green
6

Je voudrais faire valoir un point - que vous ne pouvez pas augmenter l'assemblage d'origine - en utilisant Mono.Cecil, vous pouvez injecter [InternalsVisibleTo(...)]dans l'assemblage 3pty. Notez qu'il peut y avoir des implications juridiques - vous jouez avec un assemblage vide et des implications techniques - si l'assembly a un nom fort, vous devez soit le supprimer, soit le signer à nouveau avec une clé différente.

 Install-Package Mono.Cecil

Et le code comme:

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
la source
1
Pour moi, ne peut pas être modifié signifie qu'il provient d'un nuget et je ne veux pas avoir à créer et gérer un nuget local avec les modifications. De plus, pour certaines personnes, la perte d'un nom fort importera. Mais c'est un point intéressant.
binki
3

Réflexion.

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

Utilisez le pouvoir à bon escient. N'oubliez pas la vérification des erreurs. :)

Colin Burnett
la source
-2

Eh bien, vous ne pouvez pas. Les classes internes ne peuvent pas être visibles en dehors de leur assembly, donc pas de moyen explicite d'y accéder directement -AFAIK bien sûr. Le seul moyen est d'utiliser la liaison tardive à l'exécution via la réflexion, vous pouvez alors invoquer indirectement des méthodes et des propriétés de la classe interne.

Galilyou
la source
5
Vous pouvez appeler n'importe quoi avec réflexion
MrHinsh - Martin Hinshelwood