结构包装值类型并使用索引提供访问权限
本文关键字:访问 访问权 权限 索引 包装 类型 结构 | 更新日期: 2023-09-27 17:59:17
我需要在其他结构(struct1)中封装一个用户定义的值类型的固定数组(让我们称之为struct2),但固定数组只能为本机值类型声明。所以我想创建第三个结构(结构包装器),它必须通过定义[]运算符作为struct2的数组来工作。所以
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)
}
好吧,这段代码不起作用,因为Struct2是一个值类型,当运算符返回时,它返回的是一个副本,而不是其中包含的实际对象str0。我想使用索引器访问该字段!有可能吗?为什么Array类可以实现?我知道通过返回指针是可能的,但这涉及到在运算符定义中使用fixed关键字,我希望避免这种情况,因为"数组"需要广泛访问,我最终会使用fixed语句两次(内部和外部以保持地址固定)。此外,我已经考虑过使用指针,只需在Struct1中声明N个绝对Struct2字段,并使用指向第一个字段的指针作为数组,但我更喜欢使用包装器来模拟数组行为。阿,有可能吗?
编辑似乎不可能实现与值类型一起工作的自定义数组(就像普通数组一样)。顺便说一句,我能找到的最接近的解决方案是这个,但正如我所写的,我希望避免指针。
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;
}
}
因为Struct2
是一种值类型,所以无法执行您想要的操作。编译器会给您一个错误cannot modify return value because it is not a variable.
,因为允许您执行wr[0]==123
至少会令人困惑,因为您将修改存储在array
中的值的副本,该副本将被丢弃。
我认为你应该重新考虑你的方法中的一些事情。首先,不要使用可变值类型,它们只会导致问题。当涉及到值类型时,不可变性是一种方法。其次,如果您需要可变性和引用语义,那么为什么要使用struct
呢?请考虑改用class
。
否则,最接近所需功能的方法是在包装类中将底层Struct2
数组作为readonly
字段公开。
public struct Wrapper(...)
{
public readonly Struct2[] Arr;
}
不行:
wr.Arr[0] = 123;
但你当然也可以这样做:
wr.Arr[0] = new Struct2(...)
这可能是个问题。如果可能的话,可以通过将Strutc2
构造函数设置为内部构造函数来避免这种情况。
结构被称为值语义,这就是为什么不能直接修改值的原因。
有了类,两个变量可以引用同一个对象,因此对一个变量的操作可能会影响另一个变量引用的对象。对于structs,每个变量都有自己的数据副本(ref和out参数变量除外),对其中一个变量的操作不可能影响另一个。
您应该考虑使用类而不是结构。
编辑如果您确实需要将Struct2作为结构,那么您可以将包装器创建为类。在类中,您可以拥有固定的结构数组,并通过方法对其进行修改。
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);
如果没有至少一个包含数据的根类,就无法实现您想要做的事情。这里有一个适用于问题的精简版本的解决方案,即能够访问伪数组中Struct2
的字段a
,例如:
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
如果你想修改它的内容,你的包装器必须返回一个引用类型,否则你总是会碰到访问structs时发生的值类型复制。但是您的包装器可能仍然将其数据作为一系列结构存储在内部。
这是我的解决方案:
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;
}
}
由于你关心的似乎是速度,那么没有任何包装会让你高兴。我会重新考虑整个问题,如果可能的话,写一个类来管理你的数据结构。
如果您的所有数据都可以表示为int
,那么您可能应该考虑使用一个庞大的整数数组,然后添加访问该中心数组的类,通过索引到适当的项来定位您想要操作的字段。
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;
}
}
如果需要表示各种数据类型,可以考虑对每个字段类型使用一个数组。
我认为您需要放弃这里的索引器。尽管在用户定义类型的实例和数组上使用它时看起来是一样的,但事实并非如此。当你定义一个索引器getter时,它只是Get(int index)方法的语法糖,当你从一个方法返回一个值类型时,它是按值返回的,这就是值类型的全部意义。
例如:
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
}
在不创建任何中间引用类型实例的情况下,我无法想出任何方法来使用索引器执行您想要的操作。所以这可能会留下创建基于索引设置内部结构值的方法(正如Renius所建议的)。