如果底层实现是结构,那么接口类型的变量是否充当值类型?

mao*_*o47 3 c# struct

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

public MyStruct GetThingButActuallyJustCopyOfIt() 
{ 
    return this.myStructField; 
}
Run Code Online (Sandbox Code Playgroud)

要么

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

所以我的问题是如果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";
}
Run Code Online (Sandbox Code Playgroud)

ang*_*son 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; }
}
Run Code Online (Sandbox Code Playgroud)

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

空值

好的,为什么呢?

好吧,问题是这一行:

Test(s);
Run Code Online (Sandbox Code Playgroud)

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

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

IMyInterface s = new MyStruct();
Run Code Online (Sandbox Code Playgroud)

这会改变结果吗?

是的,因为现在你在这里装箱了这个结构,并且总是使用盒装副本.在这种情况下,它的行为就像一个对象,您正在修改盒装副本并写出盒装副本的内容.

因此,无论何时打包或取消打包该结构,问题都会出现,然后您就会获得独立生活的副本.

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

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

这是一个例子.

如果我们更改Test上面的方法来获取ref参数:

public void Test(ref IMyInterface i)
Run Code Online (Sandbox Code Playgroud)

这会改变什么吗?

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

var s = new MyStruct();
Test(ref s);
Run Code Online (Sandbox Code Playgroud)

你会得到这个:

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

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

所以你将代码更改为:

IMyInterface s = new MyStruct();
Test(ref s);
Run Code Online (Sandbox Code Playgroud)

但现在你回到我的例子,只是添加了ref,我展示的并不是改变传播回来的必要条件.

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

好的,有关的更多评论ref.

是的,当然通过使用结构ref将确实使更改在整个程序中流动.

这不是这个问题的内容.问题发布了一些代码,询问它是否可行,并且会这样.在这个特定的代码变体中,它可以工作.但它很容易绊倒.并特别注意问题是关于结构和接口.如果你将接口留在其中,并使用结构传递ref,那么你有什么?一个不同的问题.

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