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
In light of your recent 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.
It seems like the real solution for you is encapsulation. If the layout of your struct
is dictated by a third-party API (so that you can interact with unmanaged code), then you should really consider wrapping the API types in appropriate classes rather than trying to interact with them directly in code.
For one, you state this:
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.
This won't turn out the way you're expecting. As others have pointed out, the only way to define a set of common functionality that can apply to structs is through interfaces (for example, the primitive types in .NET implement IComparable
). Unfortunately, if you were to declare an array of type IYourInterface
, all of the values will get boxed (interface references are reference types, even if the underlying value they're pointing to are value types).
For example, let's say you declare an IVertex
interface:
public interface IVertex
{
int SizeInBytes { get; }
void SetPointers();
}
And you have one or more value types that implement it:
struct ColorVertex : IVertex
{
Vector3 Position;
Vector4 Color;
override int SizeInBytes //static
{
get { return (3 + 4) * 4; }
}
override void SetVertexPointers() //static
{
...
}
}
Whenever you do this:
ColorVertex myVertex = new ColorVertex();
IVertex foo = myVertex;
The second line will box the value of myVertex
and store a reference to that boxed value in foo
. Since arrays are just a series of variables, the same rules apply:
IVertex[] foos = { myVertex };
All of the values in foos
will be boxed, and their references stored. This is different than if you did:
ColorVertex[] colors = { myVertex };
Where no boxing is necessary.
This has implications directly related to what you're seeking, as boxing the values now means that you no longer have a contiguous block of values (well, you do, but the contiguous block is just references; the values themselves lie elsewhere).
Given the fact that you
You should really consider wrapping the OpenGL API. For example, let's say that you have the following:
// OpenGL type
struct Vertex
{
int SizeInBytes;
}
public static extern void OpenGLFunction(Vertex[] vertices);
What is likely a better option would be to define your own interface, then hide the OpenGL API:
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);
}
}
(This is obviously a contrived example, but it should demonstrate what I'm trying to get across in terms of introducing a layer of abstraction between your code and the other API)
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.
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.