C # абстрактная структура - PullRequest
       12

C # абстрактная структура

10 голосов
/ 15 февраля 2011

Как мне добиться наследования (или аналогичного) со структурами в C #?Я знаю, что абстрактная структура невозможна, но мне нужно добиться чего-то похожего.

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

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

    abstract struct Vertex
    {
       abstract int SizeInBytes;
       abstract void SetPointers();
    }
    struct ColorVertex : Vertex
    {
       Vector3 Position;
       Vector4 Color;

       override int SizeInBytes //static
       {
          get { return (3 + 4) * 4; }
       }
       override void SetVertexPointers() //static
       {
           ...
       }
    }

class main
{
   static void main()
   {
      Vertex[] verts = new Vertex[3];
      for(int i = 0; i < 3; i++)
          verts[i] = new ColorVertex();

      verts[0].SetVertexPointers(); //should call ColorVertex.SetVertexPointers

      externalAPIcall(verts);
   }
}

РЕДАКТИРОВАТЬ:

Причина, по которой мне нужны типы значений, заключается в том, что я могу создать их массив и передать его в OpenGL в качестве буфера вершин.Для этого необходимо, чтобы данные непосредственно содержались в этом массиве.

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

Ответы [ 9 ]

7 голосов
/ 15 февраля 2011

В C # вы можете использовать интерфейсы для достижения чего-то похожего на полиморфизм с типами значений (структурами), поскольку вы не можете напрямую получить из struct, но вы можете иметь несколько типов struct, реализующих определенные интерфейсы.

Следовательно, вместо вашего резюме struct, Vertex, вы можете иметь интерфейс, IVertex.

interface IVertex
{
    int SizeInBytes { get; }
    void SetPointers();
}

Однако очень редко вам нужно реализовывать собственное значениетипы, поэтому убедитесь, что вам действительно нужна семантика типов значений, прежде чем продолжить.Если вы реализуете свои собственные типы значений, убедитесь, что они неизменяемы, поскольку изменяемые типы значений являются воротами ко всем видам ужасных проблем.

Вы должны знать, что бокс произойдет , когдаприведение типа значения к интерфейсу.Это не только имеет значение, если ваши типы значений являются изменяемыми (не создавайте изменяемые типы значений), но это уменьшит или, скорее всего, отменит любое преимущество памяти, которое вы можете получить от использования типа значений, в зависимости от того, когда или как выделать это и делать ли это для каждого значения - используйте профилировщик, если вы не уверены.

6 голосов
/ 15 февраля 2011

В свете вашего недавнего редактирования:

Причина, по которой мне нужны типы значений, состоит в том, чтобы я мог создать их массив и передать его в OpenGL в качестве буфера вершин.Для этого данные должны быть непосредственно включены в этот массив.

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

Например, вы утверждаете это:

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

Это не будет так, как вы ожидаете.Как уже отмечали другие, единственный способ определить набор общих функций, которые могут применяться к структурам, - через интерфейсы (например, примитивные типы в .NET реализуют IComparable).К сожалению, если вы объявите массив типа IYourInterface, все значения будут упакованы (ссылки на интерфейсы являются ссылочными типами, даже если базовое значение, на которое они указывают, является типами значений).

Например, допустим, вы объявляете интерфейс IVertex:

public interface IVertex
{
    int SizeInBytes { get; }
    void SetPointers();
}

И у вас есть один или несколько типов значений, которые его реализуют:

struct ColorVertex : IVertex
{
   Vector3 Position;
   Vector4 Color;

   override int SizeInBytes //static
   {
      get { return (3 + 4) * 4; }
   }
   override void SetVertexPointers() //static
   {
       ...
   }
}

Всякий раз, когда вы делаете это:

ColorVertex myVertex = new ColorVertex();

IVertex foo = myVertex;

Во второй строке будет указано значение myVertex и сохранена ссылка на это упакованное значение в foo.Поскольку массивы - это просто последовательность переменных, применяются те же правила:

IVertex[] foos = { myVertex };

Все значения в foos будут упакованы, а их ссылки сохранены.Это отличается от того, что вы сделали:

ColorVertex[] colors = { myVertex };

Там, где нет необходимости в боксе.

Это имеет значение, непосредственно связанное с тем, что вы ищете, так как бокс значений теперь означает, что вы небольше иметь непрерывный блок значений (ну, вы делаете, но непрерывный блок просто ссылки; сами значения лежат в другом месте).

Инкапсуляция

Учитывая фактчто у вас

  1. есть сторонний API с определенным типом, с которым вам нужно взаимодействовать
  2. Требование поддержки различных вариантов использования в вашем коде и желание использовать объектно-ориентированныйчтобы создать шаблоны для этого

Вы действительно должны рассмотреть обертывание OpenGL API.Например, предположим, что у вас есть следующее:

// OpenGL type
struct Vertex
{
    int SizeInBytes;
}

public static extern void OpenGLFunction(Vertex[] vertices);

Вероятно, лучшим вариантом было бы определить собственный интерфейс, а затем скрыть API OpenGL:

public abstract class VertexBase
{
    internal Vertex ToVertex()
    {
        // your logic here
    }
}

public static class OpenGL
{
    public static void WrappedFunction(VertexBase[] vertices)
    {
        Vertex[] outVertices = new Vertex[vertices.Length];

        for(int i = 0; i < vertices.Length; i++)
        {
            outVertices[i] = vertices[i].ToVertex();
        }

        OpenGLFunction(outVertices);
    }
}

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

6 голосов
/ 15 февраля 2011

Вы не можете, в основном. Вы не можете получить из структуры. Как вы думаете, почему вы хотите структуру вместо класса? Вы говорите «это должен быть тип значения» - почему? Точно так же вы думаете, что наследование является единственным вариантом вместо (скажем) композиции? Например, вы можете использовать:

public struct Vertex
{
    // ...
}

public struct Color
{
    // ...
}

public struct ColorVertex
{
    private readonly Color color;
    private readonly Vertex vertex;

    // ...
}

Вы просто не сможете получить "абстрактную структуру" или что-то похожее на работу, поэтому я предлагаю вам объяснить причины , стоящие за вашими неудовлетворенными требованиями, вместо этого просто указывать их как требования, которых нельзя избежать.

4 голосов
/ 08 октября 2012

Одна недооцененная особенность типов значений в .net заключается в том, что они могут передаваться как обобщенные типы с ограничениями интерфейса без упаковки.Например:

T returnSmaller<T>(ref T p1, ref T p2) where T:IComparable<T>
{
  return p1.CompareTo(p2) < 0 ? p1 : p2;
}

Обратите внимание, что я использовал параметры ref, чтобы исключить создание дополнительных временных копий двух параметров;дополнительная копия p2 будет в конечном итоге сделана, когда они будут переданы методу CompareTo, и по крайней мере одна дополнительная копия, вероятно, будет сделана при возврате результата, но создание двух избыточных копий будет лучше, чем создание четырех,В любом случае, вышеупомянутый метод может быть вызван без упаковки для любого типа T, который реализует IComparable<T>.

К сожалению, не существует очень приятного способа сказать «если T является одним типом структуры,передать его какому-либо методу, который принимает этот тип, в противном случае, если это какой-то другой тип, передать его методу и взять этот ".Таким образом, код, для которого потребуется конкретный точный класс (например, код, использующий API), скорее всего, должен быть не универсальным.Тем не менее, если есть некоторые методы, которые должны использоваться на различных структурах, то, если эти структуры реализуют интерфейсы , а затем передают их как ограниченные универсальные типы , могут предложить некоторые огромные преимущества.

2 голосов
/ 15 февраля 2011

Вы можете использовать интерфейсы

interface IVertex 
{
    int SizeInBytes();
    void SetPointers();
}

struct ColorVertex : IVertex
{
   Vector3 Position;
   Vector4 Color;

   int SizeInBytes
   {
      get { return (3 + 4) * 4; }
   }
   void SetVertexPointers()
   {
       ...
   }
}
2 голосов
/ 15 февраля 2011

Похоже, что вам нужен интерфейс.

public interface IVertex
{
    int SizeInBytes { get; }
    void SetPointers();
}

public struct ColorVertex : IVertex
{
   private Vector3 Position;
   private Vector4 Color;

   public int SizeInBytes
   {
      get { return (3 + 4) * 4; }
   }

   public void SetVertexPointers() // Did you mean SetPointers?
   {
   }
}

Интерфейс имеет смысл, так как все ваши методы объявлены абстрактными (это будет означать, что он использует производные классы для реализации метода, который по существучто такое интерфейс)

1 голос
/ 15 февраля 2011

Вы можете создать интерфейс IVertex, а затем добавить в Ваши структуры.

0 голосов
/ 15 февраля 2011

Возможно, вы можете использовать "union" type:

enum VertexType : byte { 
  Vertex,
  ColorVertex
}

[StructLayout(LayoutKind.Explicit)]
struct Vertex {

  [FieldOffset(0)]
  public readonly VertexType Type;

  [FieldOffset(1)]
  public readonly int SizeInBytes;

  public Vertex(VertexType type /* other necessary parameters... */) {
    Type = type;
    // set default values for fields...
    switch (Type) {
      // set the fields for each type:
      case VertexType.ColorVertex: 
        SizeInBytes = (3 + 4) * 4; 
        // other fields ...
        break;
      default: 
        SizeInBytes = 2; // or whatever...
        // other fields ...
        break;
    }
  }

  // other fields with overlapping offsets for the different vertex types...

}

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

0 голосов
/ 15 февраля 2011

Классы также могут быть " типами значений " (в том смысле, который используется в доменно-управляемом дизайне). Все, что вам нужно сделать, это сделать его неизменным, сделать конструкторы недоступными публично (защищенным или внутренним), а также создать статические фабричные методы для создания их экземпляров и управления их созданием, и при этом в ваших свойствах не будет никаких установщиков ...

ПРИМЕЧАНИЕ. Фраза Тип значения в этом контексте не имеет ничего общего с типом «Значение» против «Тип ссылки». Он имеет отношение к типу значения против типу объекта , используемому в дизайне домена Drtiven или моделировании домена ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...