Неявное продвижение интерфейса C # аналогично распаковке с использованием известного иерархического шаблона интерфейсов: - PullRequest
0 голосов
/ 13 апреля 2019

Странный вопрос здесь - что-то, что возникает из-за среды, в которой я работаю.


Немного предисловия:

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

Этот API фактически автоматически генерирует код C #, который был скомпилирован, и определяет сложную систему типов интерфейса, которая описывает 1 Интерфейс верхнего уровня, сотни интерфейсов второго уровня и сотни интерфейсов третьего уровня с соотношением 1: 1 на интерфейсах второго уровня и интерфейсах третьего уровня - IE, для каждого интерфейса второго уровня это ровно 1 интерфейс третьего уровня, который также явно реализует интерфейс второго уровня и интерфейс верхнего уровня.

Я опишу мою головоломку (грубо), используя первые 3 ранга в области таксономической системы рангов биологии.


Шаблон, который я буду использовать здесь:

  1. I%Domain% Является своего рода обобщенной заглушкой, указывающей на набор интерфейсов некоторого произвольного домена (IArchaea, IBacteria, IEukarya). Это может быть любой из этих трех (а на самом деле их буквально сотни).
  2. I%Kingdom% Является своего рода обобщенной заглушкой (похожей на I%Domain%), которая, если I%Domain% на самом деле IEukarya, будет содержать шаблоны, подобные IFungi, IProtista, IPlantae, IAnimalia. Метафора здесь разрушается, так как в действительности существует ровно 1 интерфейс этого третьего уровня, который напрямую связан с известным интерфейсом второго уровня. Однако для целей продвижения это на самом деле неважно - я просто указываю на несоответствие метафоры.
// Implemented, and "Cement". Our algorithm fundamentally works with
// IOrganism as the main type for everything, using reflection to
// iterate properties, due to the needs presented.
// Consider to be Tier-1 Interface.
interface IOrganism { /*...*/ }
// Implemented, and "Nebulous" (Could be IArchaea, IBacteria, IEukarya, etc...)
// Will never actually be IDomain, but will rather be one of
// IArchaea, IBacteria, IEukarya, in a more "generic" approach.
// Note: The %Domain% syntax is *is indicative of a
// "stub" for any arbitrary pre-defined Domain-like
// interfaces. See the pattern described above.
// Consider to be Tier-2 Interface, which is "inherited"
// from by exactly 1 Tier-3 Interface.
Interface I%Domain% : IOrganism { /*...*/ }
// Implemented, and "Nebulous". See above on the Pattern section,
// as well as the comment on I%Domain%.
// Note: The %Kingdom% is indicative of a "stub"
// for any arbitrary pre-defined Kingdom interfaces.
// Consider to be a Tier-3 Interface, for which exactly 1 exists which
// implements each Tier-2 Interface.
interface I%Kingdom% : I%Domain%, IOrganism { /*...*/ }

Вся работа выполняется на интерфейсе IOrganism, но известно, что каждый входной интерфейс для описанного метода (ниже): также I%Kingdom% (который является также I%Domain%).

Мне нужен метод в C #, который может получить входные данные IOrganism, предположить, что это I%Kingdom%, и преобразовать его в тип I%Domain% с повышенным приведением в обобщенном виде, возвращая его как IOrganism. Концептуально это похоже на распаковку, но с 3-уровневой системой и определяется через иерархические шаблоны между интерфейсами, без особого учета базовых типов объектов, только объявления интерфейсов.

// Given an IOrganism which is provided as a sub-typed
// I%Kingdom%instance , returns the input object as a
// Type-cast I%Domain%, and stored as an IOrganism.
public static IOrganism PromoteType(IOrganism organismAs%Kingdom%)
{
    // Get the type of the base as the current base type.
    // Is approximately typeof(I%Kingdom%), but
    // Isn't actually a generic, and rather refers to
    // An arbitrary instance of an I%Kingdom%-type
    // of interface.
    Type baseType = organismAs%Kingdom%.GetType();

    // Throw exception if the type provided is not the expected type
    // Note: type-checking is an abstraction,
    // we need another logical statement to determine if it
    // is the I%Kingdom% "generalized" interface type
    // Assume the actual statement works.
    if (baseType != typeof(I%Kingdom%))
    {
        // assume a more descriptive error string here.
        string error = "Provided object was of an invalid type."
        throw new InvalidArgumentException(string.Format(error));
    }

    // Stores the type of I%Domain%, inherantly.
    // Use LinQ to retrieve the matching interited type.
    // Note: The Name.Equals()-logic on "IDomain" is an abstraction
    // of the real logic, we actually have another logical statement
    // to determine if it is really the I%Domain%-pattern in
    // a more "generalized" fashion. Assume the "real"
    // variant of this statement works properly.
    Type upcastTypeAsIDomain = baseType.GetInterfaces()
        .FirstOrDefault(
            currInterfaceType => currInterfaceType.Name.Equals("I%Domain%"));

    // Note: For the third time, I%Domain% here is a logical abstraction -
    // There is another statement I'm using which works,
    // I'm just representing the effective statement
    // Relative to the question's context.
    if (upcastTypeAsIDomain != typeof(I%Domain%))
    {
        // A more meaningfull error message exists in reality.
        string error = "Provided object didn't implement the proper I%Domain% interim type.";
        throw new InvalidArgumentException(string.Format(error));
    }

    return /*This is the line I need help on*/;
}

У меня вопрос об этом операторе возврата, как мне выполнить «обобщенное» (не путать с обобщением C #) типизацию на предоставленном IOrganism, известном как порядок интерфейса I%Kingdom%, и верните его, как если бы это был I%Domain%, концептуально похожий на Unboxing в C #, зная Type объекта в целом как IOrganism, но затем приведя его к типу объявленного типа, и сохранив его, как будто это IOrganism, но где GetType () вернет соответствующий I%Domain%, а не истинный базовый I%Kingdom%? Отражение хорошо использовать здесь - я знаю о стоимости производительности, но это не будет проблемой в контексте, в котором он работает.

Я предполагаю некоторый мифический синтаксис, подобный:

// Obviously not a real Compileable line of C# - this is a pattern only.
IOrganism organismAsUpcastDomain = CAST_FROM_I%Kingdom%_TO_I%Domain%;

Существует ли какой-либо «универсальный» -каст (не путать с обобщениями C #), который преобразует из 1 типа (в качестве интерфейса) в другой тип (также в качестве интерфейса), делая вид, что базовый тип базового объекта теперь второй тип, если предположить, что иерархические определения верны? Таким образом, когда я сохраняю это organismAs%Kingdom% в IOrganism, organismAs%Kingdom%.GetType() будет возвращать тип I%Domain% вместо I%Kingdom%, несмотря на то, что принципиально все еще остается I%Kindom% внутри?

Контекст, в котором запускается эта программа, не будет «живым», в том смысле, что пользовательские запросы активно вынуждают логику выполняться, а, скорее, будут предварительно запускаться разработчиками, генерировать кеш-файл, который представляет собой результаты этой обработки, а затем могут быть просмотрены в режиме реального времени в соответствии с запросом, который будет забиваться от сотен тысяч до миллионов раз в день. Необходимо иметь возможность обрабатывать продвижение произвольного подтипа интерфейса (глубина 3) на 1 уровень вверх (глубина 2) (и храниться в типе интерфейса уровня 1).

Это может быть даже невозможно сделать в C #, так как я не уверен, насколько хорошо базовый .NET Framework различает базовый тип интерфейса, как если бы он был базовым типом базового объекта, и типом, в котором он хранится. поскольку, позволяя вам «притворяться», объект типа C действительно является типом B, хранящимся в типе A, позволяя вам вызывать .GetType() для экземпляра A, который будет возвращать тип, равный typeof(B), будучи действительно объектом типа C, фактически заставляя его лгать о своем собственном наследии.

Поначалу это может выглядеть как ковариация и контравариантность, но отличается, потому что я работаю с произвольными типами интерфейсов, которые по поведению и иерархии похожи на I%Domain% и I%Kingdom%, и описываю их с использованием отражение.


Спасибо, что даже прочитали пост здесь, так как он

  1. Long
  2. запутанный
  3. Злоупотребляет термином "универсальный", когда на самом деле не ссылается на настоящие C # Generics
  4. Должен быть ненужным, но из-за среды, в которой я программирую (я пишу программу, которая должна использовать отражение для объявленных типов в общем порядке для выполнения работы со всеми входами независимо от типов, следуя определенным заранее известным шаблонам и иерархиям, в структуре данных, которая может буквально измениться в любое время для выполнения обобщенной работы над этой абстракцией).

1 Ответ

0 голосов
/ 13 апреля 2019

Итак, я реорганизовал свою кодовую базу, что позволило мне просто вернуть отраженный родительский тип интерфейса (а не пытаться выполнить какое-то нечестивое обобщение), сделав ответ на этот вопрос спорным для моих нужд, но я 'Мы также пришли к ответу, который позволяет вам указать тип псевдонима для объекта любого другого типа.На данный момент это почти надумано, но если по какой-то причине, если вы используете отражение и хотите иметь простой механизм для отслеживания степени, в которой в сложной цепочке наследования используется текущий псевдоним, вы можете использовать следующие классы:

public abstract class TypeAlias
  {
    public virtual object ValueAsObject { get; set; }
    public virtual Type PresentingType { get; set; }
  }

  public class TypeAlias<T> : TypeAlias
    where T : class
  {
    private T underlyingTypeSafeObject;

    public TypeAlias()
      : base()
    {
      base.PresentingType = typeof(T);
    }

    public T TypeSafeObject
    {
      get
      {
        return this.underlyingTypeSafeObject;
      }

      set
      {
        this.underlyingTypeSafeObject = value;

        if (base.PresentingType == null && value != null)
        {
          base.PresentingType = value.GetType();
        }
      }
    }

    public override object ValueAsObject
    {
      get
      {
        return this.underlyingTypeSafeObject;
      }

      set
      {
        // returns null if cast conversion fails - not type-safe on the TypeAlias level.
        this.underlyingTypeSafeObject = value as T;
      }
    }

    public override Type PresentingType
    {
      get => base.PresentingType; set => base.PresentingType = value;
    }
}

С учетом этого соглашения и следующих интерфейсов (и реализаций):

  public interface IOrganism
  {
    string Be();
  }

  public interface IDomain : IOrganism
  {
    string DomainName { get; set; }
  }
  public interface IKingdom : IDomain, IOrganism
  {
    string KingdomName { get; set; }
  }

  public class SeventhDimension : IDomain
  {
    private string domainName = "7th Dimension";

    string IDomain.DomainName { get => domainName; set => domainName = value; }

    public virtual string Be()
    {
      return string.Format("{0} Exists.", this.domainName);
    }
  }

  public class KingdomHyrule : SeventhDimension, IKingdom, IDomain
  {
    private string kingdomName = "Hyrule";

    string IKingdom.KingdomName { get => kingdomName; set => kingdomName = value; }

    public virtual string Be()
    {
      string s = base.Be();
      s += string.Format(" Also, {0} Exists.", this.kingdomName);
      return s;
    }
  }

Теперь у нас может быть следующий код для предоставления другого псевдонима, которым мы можем управлятьпредставление, позволяющее по-разному размышлять над унаследованной родословной какой-либо конкретной реализации объекта:

// Create a Hyrule Kingdom, which is also a SeventhDomain,
// and IOrganism, IKingdom, IDomain.
IOrganism testOrganism = new KingdomHyrule();
Console.WriteLine(testOrganism.Be());

// Construct a TypeAlias, which by default presents
// the type of the container you store it as,
// using Generics.
TypeAlias alias = new TypeAlias<SeventhDimension>()
{
  ValueAsObject = testOrganism
};

Console.WriteLine();

// Grab the real type of the testOrganism,
// which will be KingdomHyrule
Console.WriteLine(testOrganism.GetType().Name);

// Grab the faked type of testOrganism,
// which will be SeventhDimension, due to
// the construction of the Alias.
Console.WriteLine(alias.PresentingType.Name);

// Could be retrieved using reflection on Implementing Types
alias.PresentingType = typeof(IOrganism);
Console.WriteLine(alias.PresentingType.Name);

/* Output:
* 7th Dimension Exists. Also, Hyrule Exists. | from testOrganism.Be();
*
* KingdomHyrule | from testOrganism.GetType().Name;
* SeventhDimension | from alias.PresentingType.Name;
* IOrganism | from alias.PresentingType.Name, after assignment
* */

Как я уже говорил выше - это мне даже больше не нужно, но классы TypeAlias ​​и TypeAlias<T> упрощает произвольное связывание произвольного экземпляра класса и позволяет ему представлять любой другой тип (который по соглашению может использоваться, чтобы позволить вам использовать рефлексию для захвата свойств / методов из отраженных обнаруженных базовых типов).

...