3

我在看这个问题,除了枚举某些东西的一种相当奇怪的方式之外,操作遇到了麻烦,因为枚举器是一个结构。我知道返回或传递结构使用副本,因为它是一种值类型:

public MyStruct GetThingButActuallyJustCopyOfIt() 
{ 
    return this.myStructField; 
}

或者

public void PretendToDoSomething(MyStruct thingy) 
{ 
    thingy.desc = "this doesn't work as expected"; 
}

所以我的问题是,如果 MyStruct 实现了 IMyInterface(如 IEnumerable),这些类型的方法会按预期工作吗?

public struct MyStruct : IMyInterface { ... }

//will caller be able to modify the property of the returned IMyInterface?
public IMyInterface ActuallyStruct() { return (IMyInterface)this.myStruct; }

//will the interface you pass in get its value changed?
public void SetInterfaceProp(IMyInterface thingy)
{
    thingy.desc = "the implementing type is a struct";
}
4

3 回答 3

5

是的,该代码可以工作,但需要解释,因为整个世界的代码都无法工作,除非你知道这一点,否则你很可能会陷入困境。

在我忘记之前:可变结构是邪恶的。好的,说完这些,让我们继续。

我们举个简单的例子,你可以使用LINQPad来验证这段代码:

void Main()
{
    var s = new MyStruct();
    Test(s);
    Debug.WriteLine(s.Description);
}

public void Test(IMyInterface i)
{
    i.Description = "Test";
}

public interface IMyInterface
{
    string Description { get; set; }
}

public struct MyStruct : IMyInterface
{
    public string Description { get; set; }
}

执行此操作时,将打印什么?

无效的

好的,那为什么?

好吧,问题是这一行:

Test(s);

这实际上会将该结构装箱并将装箱的副本传递给该方法。您正在成功修改该盒装副本,但不是原始s变量,该变量从未分配过任何东西,因此仍然是null.

好的,所以如果我们只更改第一段代码中的一行:

IMyInterface s = new MyStruct();

这会改变结果吗?

是的,因为现在您在这里对该结构进行装箱,并且始终使用装箱的副本。在这种情况下,它的行为就像一个对象,您正在修改装箱副本并写出装箱副本的内容。

因此,每当您对该结构进行装箱或拆箱时,问题就会出现,然后您会得到独立生活的副本。

结论:可变结构是邪恶的。

我现在看到两个关于ref在这里使用的答案,这是错误的树。使用意味着您在添加之前ref已经解决了问题。ref

这是一个例子。

如果我们把Test上面的方法改成带ref参数:

public void Test(ref IMyInterface i)

这会改变什么吗?

不,因为此代码现在无效:

var s = new MyStruct();
Test(ref s);

你会得到这个:

'UserQuery.Test(ref UserQuery.IMyInterface)' 的最佳重载方法匹配有一些无效参数

参数 1:无法从 'ref UserQuery.MyStruct' 转换为 'ref UserQuery.IMyInterface'

因此,您将代码更改为:

IMyInterface s = new MyStruct();
Test(ref s);

但是现在您回到我的示例,只是添加了ref,我展示了更改传播回来并不是必需的。

所以使用ref是正交的,它解决了不同的问题,但不是这个。

好的,关于ref.

是的,当然,在 using 周围传递一个结构ref确实会使更改在整个程序中流动。

这不是这个问题的目的。该问题发布了一些代码,询问它是否可以工作,并且可以。在这个特定的代码变体中,它会起作用。但这很容易绊倒。并特别注意问题是关于结构和接口的。如果您将接口排除在外,并在 using 周围传递结构ref,那么您有什么?一个不同的问题

添加ref不会改变这个问题,也不会改变答案。

于 2013-07-17T13:59:28.093 回答
1

在 CLR 中,每个值类型定义实际上都定义了两种东西:结构类型和堆对象类型。从结构类型到装箱对象类型存在扩大转换,从结构类型到结构类型存在缩小转换Object。结构类型将以值语义表现,而堆对象类型将以可变引用语义表现。请注意,与所有非平凡结构类型相关联的堆对象类型[即具有任何非默认状态的那些]始终是可变的,并且结构定义中的任何内容都不会导致它们改变。

请注意,值类型可以被约束、强制转换或强制为接口类型,也可以强制转换或强制为引用类型。考虑:

void DoSomethingWithDisposable<T,U>(ref T p1, 
     List<int>.Enumerator p2) where T:IDisposable
{
  IDisposable v1a = p1; // Coerced
  Object v1b = p1; // Coerced
  IDisposable v2a = (IDisposable)p2; // Cast
  Object v2b = (Object)p2; // Cast
  p1.Dispose(); // Constrained call
}
void blah( List<string>.Enumerator p1, List<int>.Enumerator p2) // These are value types
{
  DoSomethingWithDisposable(p1,p2); // Constrains p1 to IDisposable
}

将泛型类型约束为接口类型不会影响其作为值类型的行为。但是,将值类型强制转换或强制为接口或引用类型将创建堆对象类型的新实例并返回对它的引用。然后,该引用将以引用类型语义运行。

具有泛型约束的值类型的行为有时非常有用,即使在使用变异接口时也可以应用这种有用性,但不幸的是,没有办法告诉编译器值类型必须保持为值类型,并且编译器应该警告它是否会发现自己将其转换为其他东西。考虑以下三种方法:

bool AdvanceIntEnumerator1(IEnumerator<int> it)
  { return it.MoveNext(); }

bool AdvanceIntEnumerator2(ref T it) where T:IEnumerator<int>
  { return it.MoveNext(); }

bool AdvanceIntEnumeratorTwice<T>(ref T it) where T:IEnumerator<int>
  { return it.MoveNext() && AdvanceIntEnumerator1(it); }

如果将类型变量传递给第一段代码List<int>.Enumerator,系统会将其状态复制到新的堆对象,调用MoveNext该对象并放弃它。如果一个人传递了一个类型变量,该变量IEnumerator<int>包含对类型堆对象的引用List<int>.Enumerator,它将调用MoveNext该实例,调用代码仍将保留该实例。

如果将类型变量传递给第二段代码List<int>.Enumerator,系统将调用MoveNext该变量,从而改变其状态。如果传递一个类型为 的变量IEnumerable<T>,系统将调用MoveNext该变量引用的对象;该变量不会被修改(它仍将指向同一个实例),但它指向的实例将是。

将类型变量传递给第三段代码List<int>.Enumerator将导致MoveNext对该变量调用,从而改变其状态。如果返回true,系统会将已修改的变量复制到新的堆对象并调用MoveNext它。然后该对象将被放弃,因此该变量只会被推进一次,但返回值将指示第二个是否MoveNext会成功。但是,将第三段代码传递一个IEnumerator<T>包含对 a 的引用的类型变量List<T>.Enumerator将导致该实例被推进两次。

于 2013-07-17T15:44:10.227 回答
-1

不,接口是一个契约,要让它正常工作,你需要使用ref关键字。

public void SetInterfaceProp(ref IMyInterface thingy)
{
    thingy.desc = "the implementing type is a struct";
}

这里重要的是保留在该接口包装内的真实类型。

为了更清楚:

即使代码SetInterfaceProp定义了类似的方法

public void SetInterfaceProp(IMyInterface thingy)
{
    thingy.desc = "the implementing type is a struct";
}

将工作:

IMyInterface inter= default(MyStruct); 
SetInterfaceProp(inter);

这个不会

MyStruct inter = default(MyStruct); 
SetInterfaceProp(inter);

您不能保证您的方法的调用者将始终使用IMyInterface,因此为了保证预期的行为,在这种情况下,您可以定义ref关键字,这将保证在这两种情况下方法都会按预期运行。

于 2013-07-17T13:51:07.660 回答