C# abstract struct

2020-03-11 09:55发布

How can I achieve inheritance (or similar) with structs in C#? I know that an abstract struct isn't possible, but I need to achieve something similar.

I need it as a struct because it has to be a value type. And I need inheritance because I need a generic array and methods that I can garantee are there.

I think an example will go a long way, so heres some code that won't compile, but shows what I want to achieve;

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

EDIT:

The reason I need value types is so that I can make an array of them, and pass it to OpenGL as a vertex buffer. For this, the data needs to be directly contained in this array.

I'd be surprised if this were simply not possible to achieve.

9条回答
干净又极端
2楼-- · 2020-03-11 10:14

Maybe you can use a "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...

}

Just remember to make it immutable and to access only the fields that make sense for each type.

查看更多
三岁会撩人
3楼-- · 2020-03-11 10:15

You can create interface IVertex and then add to Your structs.

查看更多
劳资没心,怎么记你
4楼-- · 2020-03-11 10:17

In C#, you can use interfaces to achieve something akin to polymorphism with value types (structs) as you can't derive directly from a struct but you can have multiple struct types implement specific interfaces.

Therefore, instead of your abstract struct, Vertex, you can have an interface, IVertex.

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

However, it is exceedingly rare that you need to implement your own value types, so make sure you really need value type semantics before proceeding. If you do implement your own value types, make sure they're immutable as mutable value types are a gateway to all kinds of horrible problems.

You should be aware that boxing will occur when casting from a value type to an interface. Not only does this have implications if your value types are mutable (don't make mutable value types), but this will decrease, or most likely cancel out any memory advantage you may gain from using a value type, depending on when or how you do this and whether you do it for every value - use a profiler if you're unsure.

查看更多
再贱就再见
5楼-- · 2020-03-11 10:23

One under-appreciated feature of value types in .net is that they can be passed as interface-constrained generic types without boxing. For example:

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

Note that I used ref parameters to eliminate making extra temporary copies of the two parameters; an extra copy of p2 will end up being made when they are passed to the CompareTo method, and at least one extra copy will likely be made when the result is returned, but making two redundant copies would be better than making four. In any case, the above method may be invoked without boxing on any type T which implements IComparable<T>.

Unfortunately, there's no terribly nice way of saying "if T is one type of struct, pass it to some method which takes that type; otherwise if it's some other type, pass it to a method and take that one". Thus, code which will require a specific exact class (like the code using the API's) will likely have to be non-generic. Nonetheless, if there are some methods which should be usable on a variety of structs, having those structs implement interfaces and then passing them as constrained generic types may offer some huge advantages.

查看更多
神经病院院长
6楼-- · 2020-03-11 10:28

Classes can be "value types" as well, (in the sense used in Domain Driven Design). All you have to do is make it immutable, make the constructors inaccessible publicly (Protected or internal), and create static factory methods to create instances of them and control their instantiation, and do not have any setters on your properties...

NOTE: The phrase Value Type in this contect has nothing to do with Value type vs Reference Type. It has to do with Value Type vs Entity Type as used in Domain Drtiven Design or Domain Modeling...

查看更多
ゆ 、 Hurt°
7楼-- · 2020-03-11 10:30

You can use interfaces

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

struct ColorVertex : IVertex
{
   Vector3 Position;
   Vector4 Color;

   int SizeInBytes
   {
      get { return (3 + 4) * 4; }
   }
   void SetVertexPointers()
   {
       ...
   }
}
查看更多
登录 后发表回答