Bagaimana cara mengakses kelas internal dari rakitan eksternal?

97

Memiliki rakitan yang tidak dapat saya modifikasi (disediakan vendor) yang memiliki metode yang mengembalikan tipe objek tetapi sebenarnya merupakan tipe internal.

Bagaimana cara mengakses bidang dan / atau metode objek dari perakitan saya?

Perlu diingat bahwa saya tidak dapat mengubah rakitan yang disediakan vendor.

Intinya, inilah yang saya miliki:

Dari vendor:

internal class InternalClass
  public string test;
end class

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

Dari perakitan saya menggunakan perakitan vendor.

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

Jawaban:

83

Tanpa akses ke jenis (dan tidak ada "InternalsVisibleTo" dll) Anda harus menggunakan refleksi. Tetapi pertanyaan yang lebih baik adalah: haruskah Anda mengakses data ini? Itu bukan bagian dari kontrak tipe publik ... kedengarannya bagi saya seperti itu dimaksudkan untuk diperlakukan sebagai objek buram (untuk tujuan mereka, bukan milik Anda).

Anda telah mendeskripsikannya sebagai bidang contoh publik; untuk mendapatkan ini melalui refleksi:

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

Jika ini sebenarnya adalah properti (bukan bidang):

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

Jika non-publik, Anda harus menggunakan BindingFlagskelebihan GetField/ GetProperty.

Selain penting : berhati-hatilah dengan refleksi seperti ini; penerapannya dapat berubah di versi berikutnya (merusak kode Anda), atau dapat disamarkan (merusak kode Anda), atau Anda mungkin tidak memiliki cukup "kepercayaan" (melanggar kode Anda). Apakah Anda melihat polanya?

Marc Gravell
sumber
Marc Saya bertanya-tanya ... dimungkinkan untuk mengakses bidang / properti pribadi tetapi apakah ada cara untuk melemparkan objek yang dikembalikan oleh GetValue menggunakan tipe yang benar?
codingadventures
1
@GiovanniCampo jika Anda tahu secara statis apa jenisnya: yakin - baru saja cor. Jika Anda tidak mengetahui jenisnya secara statis - tidak jelas apa artinya ini
Marc Gravell
Bagaimana dengan orang yang mempublikasikan sesuatu sebagai internal yang benar-benar merupakan bagian dari API publik? Atau mereka menggunakan InternalsVisibleTotetapi tidak menyertakan rakitan Anda? Jika simbol tidak benar-benar tersembunyi, itu adalah bagian dari ABI.
binki
208

Saya hanya melihat satu kasus di mana Anda mengizinkan pemaparan anggota internal Anda ke majelis lain dan itu untuk tujuan pengujian.

Mengatakan bahwa ada cara untuk mengizinkan rakitan "Teman" mengakses internal:

Dalam file AssemblyInfo.cs proyek Anda menambahkan baris untuk setiap perakitan.

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

info ini tersedia di sini.

Semoga ini membantu.

zonkflut.dll
sumber
Memperluas kasus tujuan pengujian. Skenario apa pun di mana Anda telah menggabungkan internal dalam konteks yang berbeda. Misalnya, di Unity, Anda mungkin memiliki rakitan runtime, rakitan pengujian, dan rakitan editor. Runtime internal harus terlihat untuk test dan editor assemblies.
Steve Buzonas
3
Saya tidak berpikir jawaban ini membantu - OP mengatakan bahwa dia tidak dapat memodifikasi perakitan vendor, dan jawaban ini berbicara tentang memodifikasi file AssemblyInfo.cs dari proyek vendor
Simon Green
6

Saya ingin membantah satu hal - bahwa Anda tidak dapat menambah rakitan asli - menggunakan Mono.Cecil, Anda dapat menyuntikkan [InternalsVisibleTo(...)]ke rakitan 3pty. Perhatikan mungkin ada implikasi hukum - Anda mengotak-atik perakitan 3pty dan implikasi teknis - jika rakitan memiliki nama yang kuat, Anda perlu menghapusnya atau menandatanganinya kembali dengan kunci yang berbeda.

 Install-Package Mono.Cecil

Dan kodenya seperti:

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
sumber
1
Bagi saya, tidak dapat dimodifikasi berarti berasal dari nuget dan saya tidak ingin membuat dan mengelola nuget lokal dengan modifikasi. Selain itu, bagi sebagian orang, kehilangan nama yang kuat akan menjadi masalah. Tapi ini poin yang menarik.
binki
3

Refleksi.

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

Gunakan kekuatan dengan bijak. Jangan lupa pengecekan error. :)

Colin Burnett
sumber
-2

Ya, Anda tidak bisa. Kelas internal tidak dapat terlihat di luar rakitan mereka, jadi tidak ada cara eksplisit untuk mengaksesnya secara langsung -AFAIK tentunya. Satu-satunya cara adalah dengan menggunakan runtime late-binding melalui refleksi, lalu Anda bisa memanggil metode dan properti dari kelas internal secara tidak langsung.

Galilyou
sumber
5
Anda dapat memanggil apapun dengan refleksi
MrHinsh - Martin Hinshelwood