3

我想在一个数组中保存对多个短裤的引用。我以为我可以创建短裤,然后将它们添加到数组中。所以……每次被引用的对象改变时,都会反映在数组中,反之亦然。做一些试验使我确信它不是那样工作的。事实上,看起来价值被转移但不是参考。

下面的代码创建了两个短裤,将它们作为对象添加到数组中,然后更改原始短裤。但是,当访问数组中假定的引用短时,它并没有改变,这让我相信它是一个全新的对象,与原始对象无关。

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

我在这里误解了一些基本的东西,如果有人能解释一下,我会很感激,也许会指出一个可以做我想做的解决方案。

4

6 回答 6

18

C#类型系统中有“值类型”和“引用类型”两种类型。

值类型是按值复制的;当您复制一个时,您会得到一个与原始对象无关的全新对象。

引用类型通过引用复制;当您复制一个时,您实际上是在复制对某个存储位置的引用。您会得到两个引用,它们都引用一个对象。

短裤是价值类型。

如果你想让一个 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);

你去吧。

现在,这将为您提供对 short的引用访问权限,因为 short 实际上存储在与类的 value 属性关联的字段中。如果你接着说:

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”元素类型的数组或存储 ref 的字段。所以从这个意义上说,你不能做你想做的事。

C# 确实支持一些特殊的“隐藏”功能,用于传递类似于变量引用但比上面提到的“两个委托”引用更轻的对象。但是,这些特殊功能仅适用于奇怪的互操作场景,我建议不要使用它们。(同样,你不能创建一个存储类型引用的数组。)我认为我不会在这个答案中更多地讨论这些特性;你真的不想去那里,相信我。

于 2011-02-04T16:36:37.750 回答
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包装类到包装类并返回的转换器)。

于 2011-02-04T16:36:06.157 回答
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也会改变。

于 2011-02-04T16:37:09.900 回答
1

值类型之所以称为值类型,是因为它们在传递给方法或通过 = 运算符分配时按值传递。

另一种(也是更正确的)看待它的方式是短裤、整数等是不可变的 => 它们无法更改。所以你基本上不能改变短路。如果你需要一个 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 类型的对象替换为新对象。

顺便说一句,如果您需要包裹裸短裤,您的设计可能有问题。您应该已经在使用一些更复杂的对象,或者应该以其他方式使用短裤数组。但我猜你只是在测试。

于 2011-02-04T16:39:39.597 回答
0

根本问题是它short是一个结构而不是一个对象。所以基本上一个数组short实际上是一个数组short而不是一个对短对象的引用数组。

为了解决这个问题,你可以在课堂上“装箱”(但这会很乏味)

尝试以下方法:

public class MyShort { public Value { get; set; } }
于 2011-02-04T16:34:22.757 回答
0

如果您向类添加转换运算符,您可以ReferenceType透明地使用,就好像 float、int 等实际上是引用类型:

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,您可以要求这些转换的强制转换,如果您想以稍微冗长为代价使事情更清晰。

于 2018-01-03T18:17:26.523 回答