C#:整数数组的引用/指针数组

3
我想将一些shorts的引用保存在一个数组中。我以为只需创建shorts,然后将它们添加到数组中即可。所以......每次更改引用对象时,都会反映在数组中,反之亦然。经过一些实验,我认为它并不完全是这样的。事实上,它看起来像是传递了值但没有传递引用。
以下代码创建了两个shorts,将其作为对象添加到数组中,然后更改了原始shorts。但是,在访问数组中假定的引用shorts时,它没有发生变化,这让我相信它是一个全新的对象,与原始对象无关。
        Console.WriteLine("Testing simple references:");
        short v1 = 1;
        short v2 = 2;
        object[] vs = new object[2];
        vs[0] = v1;
        vs[1] = v2;
        v1 = 1024;
        v2 = 512;
        Console.WriteLine(" v1: " + (short)vs[0]);
        Console.WriteLine(" v2: " + (short)vs[1]);

我在这里误解了一些基本的东西,希望有人能够解释一下,并可能为我指出一个解决方案,以实现我想要的功能。

6个回答

19
在C#类型系统中有两种类型,即"值类型"和"引用类型"。
值类型是按值进行复制的;当你复制一个值类型时,你会得到一个与原始对象无关的全新对象。
引用类型是按引用进行复制的;当你复制一个引用类型时,实际上是复制了指向某个存储位置的引用。你会得到两个引用,它们都指向同一个对象。
Shorts 是值类型。
如果你想让 short 成为引用类型,那么你可以创建一个引用类型的包装器。
class ReferenceType<T> where T : struct
{
    public T Value { get; set }
    public ReferenceType(T value) { this.Value = value; }
}

var v1 = new ReferenceType<short>(1);
var v2 = new ReferenceType<short>(2);
var vs = new ReferenceType<short>[2] { v1, v2 };
v1.Value = 1024;
v2.Value = 512;
Console.WriteLine(vs[0].Value);
Console.WriteLine(vs[1].Value);

现在你可以通过引用访问the short,因为短语实际上存储在与类的值属性相关联的字段中。如果您接下来说:

v2 = new ReferenceType<short>(3);
Console.WriteLine(vs[1].Value);

您将不会获得 "3" -- v2 现在指向的是一个与 vs[1] 不同的对象。如果您真正想要捕获的是 变量 的引用,那么您需要使用的是一个 闭包

class ReferenceToVariable<T>
{
    private Func<T> getter;
    private Action<T> setter;
    public ReferenceToVariable(Func<T> getter, Action<T> setter) 
    { 
        this.getter = getter;
        this.setter = setter;
    }
    public T Value { get { return getter(); } set { setter(value); } }
}
...
short v1 = 1;
short v2 = 2;
var vs = new [] 
{ 
    new ReferenceToVariable<short>(()=>v1, x=>{v1=x;}),
    new ReferenceToVariable<short>(()=>v2, x=>{v2=x;})
};
v1 = 123;
vs[1].Value = 456;
Console.WriteLine(vs[0].Value); // 123
Console.WriteLine(v2); // 456

这里我们将对象捕获到数组中,这些对象知道如何获取和设置v1和v2的当前值。

现在,如果你想直接创建一个别名指向另一个变量,而不需要通过这个对象,那么在C#中只有一种方法:

void M(ref short x)
{
    x = 123;
}
...
short y = 1;
M(ref y);

现在,“x”和“y”是同一个变量的两个名称。然而,“将别名指向另一个变量”的概念仅在C#中当别名变量为方法的形式参数时才起作用。一般情况下无法实现。
理论上,我们可以做一些像你所想的事情。我们可以支持“ref locals”:
更新:我在这里讨论的“理论”功能已添加到C# 7.0中。
short v1 = 1;
ref short rv1 = ref v1;
rv1 = 123;
Console.WriteLine(v1); // 123

也就是说,rv1成为v1的别名。C#不支持这个功能,但CLR支持,因此我们可以支持它。然而,CLR不支持创建“ref”元素类型的数组或存储引用的字段。因此,在这个意义上,你不能做你想要的事情。
C#支持一些特殊的“隐藏”功能,用于传递像变量引用一样的对象,但比上面提到的“两个委托”引用更轻量级。然而,这些特殊功能仅用于奇怪的互操作场景,我建议不要使用它们。(而且,你不能创建存储类型引用的数组。)我不认为我会在这个答案中进一步讨论这些功能;相信我,你真的不想去那里。

1
我会在那个类上添加一个 where T : struct。虽然不是必须的,但更符合类型预期实现的概念。 - Joel Coehoorn
由于Eric没有提供关于C#“隐藏”功能的详细信息,我已经发布了一个链接:https://dev59.com/b-o6XIcBkEYKwwoYTzRZ#45398 - Pop Catalin

5

Short是一种值类型,但你试图让它像引用类型一样运作。

你可以创建一个带有short属性的类,然后使用该类的数组:

public class MyShort
{
    public short Value {get; set;}
}

public class SomeOtherClass
{
   public void SomeMethod()
   {
       MyShort[] array = new MyShort[2];
       array[0] = new MyShort {Value = 5};
       array[1] = new MyShort {Value = 2};

       array[0].Value = 3;
   }
}

有可能你可以做一些工作来使它更加顺畅(例如实现从short到你的包装类的转换器以及相反的转换)。


3

short类型是一种值类型,不像引用类型那样按照你期望的方式工作。当你将一个值类型赋给一个变量时,它的值被赋值,而不是它的引用。vs[0]将保存你赋给v1的值的副本。

如果你确实需要在更改原始值时更改数组中的值,你需要将你的short包装在一个引用类型中。这里有一个例子:

public class ShortHolder {
  public short Value { get; set; }
}

然后您可以像这样使用它:
var v1 = new ShortHolder() { Value=123; }
var shortArray = new ShortHolder[1];
shortArray[0] = v1;

如果您更改v1.Value,那么shortArray[0].Value也会发生变化。

1

值类型被称为值类型,因为当它们被传递给方法或通过 = 运算符分配时,它们是按值传递的。

另一种(更正确的)看待方式是,shorts、ints等是不可变的 => 它们不能被改变。所以你基本上不能改变一个 short。如果你需要一个 short 类型的对象在某处改变,你需要创建一个类来保存这个对象,像这样:


public class ShortWrapper
{
    public short ShortValue {get; set;}
}
class Program
{
    static void Main(string[] args)
    {
        ShortWrapper short1 = new ShortWrapper{ ShortValue = 1};
        ShortWrapper short2 = new ShortWrapper { ShortValue = 2 };

        ShortWrapper[] shorts = new ShortWrapper[] { short1, short2 };
        shorts[0].ShortValue = 5;

        Console.WriteLine(short1.ShortValue);
    }
}

本质上,该代码将类型为short的对象替换为新对象。

顺便说一句,如果您需要包装一个裸露的short,那么您的设计可能存在问题。您应该已经使用了一些更复杂的对象,或者以其他方式处理shorts数组。但我猜您只是在测试。


0

根本问题在于short是一个结构体而不是对象。因此,基本上short数组实际上是short数组,而不是对短对象的引用数组。

要解决这个问题,您可以将short装箱到类中(但这将很繁琐)

请尝试以下操作:

public class MyShort { public Value { get; set; } }

我很震惊地发现short是一个结构体。以下是一些链接,供后来者确认:http://msdn.microsoft.com/en-us/library/ybs77ex4.aspx,http://msdn.microsoft.com/en-us/library/system.int16.aspx - Dev Kanchen

0

如果您在类中添加转换运算符,您可以像使用float、int等引用类型一样透明地使用ReferenceType

class ReferenceType<T> where T : struct
{
    public T Value { get; set; }
    public ReferenceType(T value) { this.Value = value; }
    public static implicit operator ReferenceType<T>(T b)
    {
        ReferenceType<T> r = new ReferenceType<T>(b);
        return r;
    }
    public static implicit operator T(ReferenceType<T> b)
    {
        return b.Value;
    }
}
ReferenceType<float> f1 = new ReferenceType(100f);
f1 = 200f;
float f2 = f1;

使用explicit关键字而非implicit,在需要进行类型转换时会强制进行显式转换,这样可以更加清晰地表达代码意图,但也稍微增加了一些冗余。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接