Reputation: 324
I need to encapsulate an fixed array of a user defined value type (let's call it struct2 ) inside an other struct (struct1) but fixed arrays can be declared only for native value types. So i thought to create a third struct (struct wrapper) that has to work as an array of the struct2 by defining the [] operator. So
struct Struct2
{
int a;
int b
}
struct wrapper
{
Struct2 str0;
Struct2 str1;
public Struct2 this[int index]
{
get
{
switch ( index )
{
case 0: return str0;
case 1: return str1;
default: return str0;
}
}
}
}
static void Main()
{
wrapper wr = new wrapper();
wr[0].a = 123; // i would like to do this but this doesnt work
//but if we do like this
Struct2 [] arr = new Struct2[5];
arr[0].a = 123 ;// this works , we are effectively modifying the object
//contained in the array (not just a copy)
}
Ok, this code doesn't work because Struct2 is a value type and when the operator returns then it returns a copy and not the actual object str0 contained in it. I would like to access that field using an indexer ! is it possible ? Why it's possible with the Array class ? I know that it's possible by returning a pointer but this involve using the fixed keyword inside the operator definition and i would like to avoid that because the 'array' need to be accessed extensively and i would end up by using the fixed statement 2 times (inside and outsid to keep the address fixed). Also i already considered using pointers by just declaring N adiacent Struct2 fields in Struct1 and using a pointer to the first one as an array but i would prefer to use the wrapper to emulate an array behaviour. Agai , is it possible?
Edit It seems that it's not possible to implement a custom array that works with value types(as common arrays do). Btw the nearest solution i could find is this, but as i wrote i would have liked to avoid pointers.
struct Struct2
{
int a;
int b
}
struct Struct1
{
public Struct2 * str2arr ; //this can be avoided
public Struct2 str0;
//wrapper is not needed anymore as Struct1 is wrapping the Struct2 array by itself
//and a pointer is used for indexing operations
//struct layout needs to be sequential
private Struct2 str1;
private Struct2 str2;
private Struct2 str3;
private Struct2 str4;
}
static void Main()
{
Struct1 myStruct = new Struct1();
fixed(myStruct.str2arr = &myStruct.str0)
{
myStruct.str2arr[1] = 123;
}
}
Upvotes: 2
Views: 2603
Reputation: 1063
I think you need to give up on the indexer here. Even though it looks the same when using it on an instance of a user defined type and an array it's not. When you define an indexer getter it's just syntactical sugar for a Get(int index) method and when you return a value type from a method it is returned by value, that's the whole point of a value type.
For example:
struct wrapper {
public Struct2 str0;
public Struct2 str1 { get; set; }
public Struct2 this[int index] {
get {
switch ( index ) {
case 1: return str1;
default: return str0;
}
}
}
}
static void Main(string[] args) {
wrapper wr = new wrapper();
wr.str0.a = 123; // Works, accessing fields only.
wr.str1.a = 123; // Does not work, str1 is a property, which is really a method
wr[0].a = 123; // Also does not work
}
I can't come up with any way of doing what you want with an indexer without creating any intermediate reference type instances. So this probably leaves creating methods for setting the the values of the inner struct based on index (as suggested by Renius).
Upvotes: 1
Reputation: 10527
You cannot achieve what you want to do without having at least a root class containing your data. Here is a solution which works for a stripped-down version of your problem, namely being able to access field a
of your Struct2
in your pseudo-array, such as:
wrapper wr = new wrapper (); // needs to be a class
wr[0].a = 123;
wr[1].a = 456;
System.Console.WriteLine ("wr[0].a = {0}", wr[0].a); // displays 123
System.Console.WriteLine ("wr[1].a = {0}", wr[1].a); // displays 456
Your wrapper must return a reference type if you want to be able to modify its contents, or else you will always hit your head against the value-type copying taking place when you access structs. But your wrapper may still be storing its data internally as a series of structs.
Here is my solution:
struct Struct2
{
public int a;
}
class wrapper // sorry, cannot use 'struct' here ...
{
Struct2 str0;
Struct2 str1;
public helper this[int index]
{
get
{
return new helper (this, index);
}
}
int GetValueA(int index)
{
switch (index)
{
case 0: return str0.a;
case 1: return str1.a;
default: throw new System.IndexOutOfRangeException ();
}
}
void SetValueA(int index, int value)
{
switch (index)
{
case 0: str0.a = value; break;
case 1: str1.a = value; break;
}
}
public class helper
{
public helper(wrapper host, int index)
{
this.host = host;
this.index = index;
}
public int a
{
get { return this.host.GetValueA (index); }
set { this.host.SetValueA (index, value); }
}
private readonly wrapper host;
private readonly int index;
}
}
As your concern seems to be speed, then no wrapper will make you happy. I would reconsider the whole problem and, if at all possible, write a class to manage your data structure.
If all your data can be represented as int
, maybe you should consider using a huge array of integers and then add classes which access that one central array to locate the fields you want to manipulate, by indexing into the proper item.
class Wrapper
{
...
int[] data;
public StructWrapper1 this[int index]
{
get
{
return new StructWrapper1 (this, index);
}
}
public class StructWrapper1
{
public StructWrapper1(Wrapper wrapper, int index)
{
this.wrapper = wrapper;
this.index = index;
}
public int A
{
get { return this.wrapper[this.index*2+0]; }
set { this.wrapper[this.index*2+0] = value; }
}
public int B
{
get { return this.wrapper[this.index*2+1]; }
set { this.wrapper[this.index*2+1] = value; }
}
private readonly Wrapper wrapper;
private readonly int index;
}
}
If you need to represent various data types, you could consider using one array for every field type.
Upvotes: 1
Reputation: 11820
Struct is known as value semantics that's why you cant modify value directly.
With classes, it is possible for two variables to reference the same object, and thus possible for operations on one variable to affect the object referenced by the other variable. With structs, the variables each have their own copy of the data (except in the case of ref and out parameter variables), and it is not possible for operations on one to affect the other.
You should think about using classes instead structs.
EDIT If you really need to have Struct2 as struct then you can create wrapper as class. In class you can have fixed array of structures and modify it through method.
class wrapper
{
Struct2[] structArray = new Struct2[10];
public void setValues(int index, int a, int b)
{
structArray[index].a = a;
structArray[index].b = b;
}
}
wrapper wr = new wrapper();
wr.setValues(0, 2, 3);
Upvotes: 1
Reputation: 32770
You cant do what you want because Struct2
is a value type. The compiler will give you an error cannot modify return value because it is not a variable.
because allowing you to do wr[0]==123
would be, to say the least, confusing as you would be modifying a copy of the value stored in the array
which would be discarded.
I think you should reconsider quite a few things in your approach. First of all, do not use mutable value types, they only cause problems. Immutability is the way to go when it comes to value types. Second of all, if you need mutability and reference semantics then why use a struct
at all? Consider using a class
instead.
Otherwise, the closest you can get to the functionality you are looking for is exposing in your wrapper class the underlying Struct2
array as a readonly
field.
public struct Wrapper(...)
{
public readonly Struct2[] Arr;
}
No you can do:
wr.Arr[0] = 123;
But of course you can also do:
wr.Arr[0] = new Struct2(...)
which could be a problem. You can circumvent this by makingStrutc2
constructors internal if possible.
Upvotes: 1