Как я могу получить доступ к внутреннему классу из внешней сборки?

97

Имея сборку, которую я не могу изменить (предоставляется поставщиком), которая имеет метод, возвращающий тип объекта, но на самом деле имеет внутренний тип.

Как я могу получить доступ к полям и / или методам объекта из моей сборки?

Имейте в виду, что я не могу изменять сборку, предоставленную поставщиком.

По сути, вот что у меня есть:

От продавца:

internal class InternalClass
  public string test;
end class

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

Из моей сборки с использованием сборки производителя.

public class MyClass
{
  public void AccessTest()
  {
    Vendor vendor = new Vendor();
    object value = vendor.Tag;
    // Here I want to access InternalClass.test
  }
}
Stécy
источник

Ответы:

83

Без доступа к типу (и без «InternalsVisibleTo» и т. Д.) Вам пришлось бы использовать отражение. Но лучший вопрос: должны ли вы иметь доступ к этим данным? Это не часть контракта открытого типа ... мне кажется, что он предназначен для обработки как непрозрачный объект (для их целей, а не для ваших).

Вы описали его как публичное поле экземпляра; чтобы получить это через отражение:

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

Если это действительно свойство (а не поле):

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

Если он не является общедоступным, вам нужно будет использовать BindingFlagsперегрузку GetField/ GetProperty.

Важное замечание : будьте осторожны с подобным отражением; реализация может измениться в следующей версии (нарушить ваш код), или она может быть запутана (нарушен ваш код), или вам может не хватить «доверия» (нарушение вашего кода). Вы замечаете закономерность?

Марк Гравелл
источник
Марк. Интересно ... можно получить доступ к частным полям / свойствам, но есть ли способ привести объект, возвращаемый GetValue, с использованием правильного типа?
codingadventures
1
@GiovanniCampo, если вы статически знаете, что это за тип: конечно - просто приведите. Если вы не знаете тип статически - непонятно, что это могло бы вообще значить
Марк Грейвелл
А как насчет людей, которые публикуют вещи как внутренние, которые на самом деле являются частью общедоступного API? Или они использовали, InternalsVisibleToно не включили вашу сборку? Если символ не скрыт на самом деле, это часть ABI.
binki
208

Я вижу только один случай, когда вы разрешили бы доступ своим внутренним членам к другой сборке, и это для целей тестирования.

Говоря, что есть способ разрешить "Другим" сборкам доступ к внутренним компонентам:

В файле AssemblyInfo.cs проекта вы добавляете строку для каждой сборки.

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

эта информация доступна здесь.

Надеюсь это поможет.

Zonkflut
источник
Расширяемся на случай тестирования. Любой сценарий, в котором вы объединили внутренние компоненты в разных контекстах. Например, в Unity у вас может быть сборка среды выполнения, тестовая сборка и сборка редактора. Внутреннее устройство среды выполнения должно быть видимым для сборок тестирования и редактора.
Стив Бузонас
3
Я не думаю, что этот ответ помогает - OP говорит, что он не может изменять сборку поставщика, и в этом ответе говорится об изменении файла AssemblyInfo.cs проекта поставщика
Саймон Грин
6

Я хотел бы поспорить с одним моментом - вы не можете расширить исходную сборку - используя Mono.Cecil, вы можете внедрить [InternalsVisibleTo(...)]в сборку 3pty. Обратите внимание, что могут быть юридические последствия - вы возитесь со сборкой 3pty и техническими последствиями - если сборка имеет строгое имя, вам нужно либо удалить его, либо повторно подписать с другим ключом.

 Install-Package Mono.Cecil

И такой код:

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
Для меня «невозможно изменить» означает, что он исходит от nuget, и я не хочу создавать и управлять локальным nuget с изменениями. Также для некоторых людей потеря сильного имени будет иметь значение. Но это интересный момент.
binki
3

Отражение.

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

Используйте силу с умом. Не забывайте о проверке ошибок. :)

Колин Бернетт
источник
-2

Ну не можешь. Внутренние классы не могут быть видны вне своей сборки, поэтому нет явного способа получить к нему прямой доступ - конечно, AFAIK. Единственный способ - использовать позднее связывание во время выполнения через отражение, тогда вы можете косвенно вызывать методы и свойства из внутреннего класса.

Галилю
источник
5
Вы можете называть что угодно с отражением
MrHinsh - Мартин Хиншелвуд