反射中的OutAttribute和out修饰符有何区别?

3
如果我写下这个代码:
public interface IOutModifier
{
    void OutModifier(out int a);
}

尝试在接口中实现它,VS会生成如下代码(预期结果):

public class IOM : IOutModifier
{
    public void OutModifier(out int a)
    {
        throw new NotImplementedException();
    }
}

如果我写下这个代码:
public interface IOutAndOutAttributeModifier
{
    void OutAndOutAttributeModifier([Out] out int a);
}

Visual Studio会像这样实现:

public class IOOAM : IOutAndOutAttributeModifier
{
    public void OutAndOutAttributeModifier([Out]out int a)
    {
        throw new NotImplementedException();
    }
}

侧记:写下如下内容:
public interface IOutAttributeModifier
{
    void OutAttributeModifier([Out] int a);
}

将实现如下方式:
public class IOAM : IOutAttributeModifier
{
    public void OutAttributeModifier([Out] int a)
    {
        throw new NotImplementedException();
    }
}

所以,似乎有一种方法可以区分是否存在OutAttribute...但是我无法找出如何(通过Reflection)。在这两种情况下,获取自定义属性信息的任何方法(GetCustomAttributes(),GetCustomAttributeData()等)都会报告OutAttribute存在于所有接口方法上。这也不是当前项目中现有代码的情况 - 如果我引用一个具有这些接口的程序集,则VS仍将生成上面显示的相同代码。

那么,我怎样才能区分一个只是"out"的参数和一个明确添加了"[Out]"属性的参数呢?


分享一下你尝试过的内容。 - James Lucas
@JamesLucas:他确实分享了。就在问题中呢。 - Jeroen Vannevel
@JamesLucas,我已经查看了ParameterInfo的GetCustomAttributes(),GetCustomAttributeData(),Attributes属性等。一个带有[Out]和out修饰符以及只有out的参数之间似乎没有区别。如果是这样的话,那就没问题了,我只是想确保我没有漏掉什么。 - JasonBock
@JasonBock:我似乎也找不到任何解决方案。它必须编译成完全相同的代码,使得反向过程成为不可能的任务。 - Jeroen Vannevel
顺便问一下,你为什么需要它?你想要解决什么问题? 对我来说,这听起来像是一个 XY 问题。 - Sriram Sakthivel
显示剩余3条评论
2个回答

1

实际上,你们两个的代码并不相同。

IOM是正确使用输出参数的方式。IOAM是错误的。

例如,尝试使用值而不是变量调用该方法。它应该无法编译通过。因为传递给参数的out参数必须是一个变量而不是一个值。

IOM iom = new IOM();
iom.OutModifier(4);//Fails as expected

IOAM ioam = new IOAM();
ioam.OutAttributeModifier(4);//Compiles fine

这是因为输出参数必须通过引用传递。在你的例子IOAM中,你以值的形式传递它。
IOM的MSIL代码为:
.method public hidebysig newslot virtual final instance void OutModifier([out] int32& a) cil managed

IAOM的MSIL代码为:

.method public hidebysig newslot virtual final instance void OutAttributeModifier([out] int32 a) cil managed

在IAOM.OutAttributeModifier中,注意参数a没有通过引用传递。
我认为这是你这种情况下的疏忽。如果这是有意的,那么你可以通过检查参数是否通过ref传递来区分它。你可以通过调用ParameterInfo.ParameterType.IsByRef来实现这一点。

它仍然可以编译,但你仍然可以向不是“out”的参数添加[Out]。你是否能够正确调用它并以预期的方式调用它并不是我关注的重点。我是根据给我的程序集中的内容生成代码的。查看一下FileStream上的Read方法,它在byte[]上具有[In,Out],但它不是“out”。还有带有[Out]而没有“out”修饰符的方法。再次强调,我不太关心“正确性”,只关心它们的存在以及如何处理它们。 - JasonBock
@JasonBock 在你的示例中,你可以通过 ParameterInfo.ParameterType.IsByRef 进行检查。第一个将返回 true,而后者将返回 false。如果两个 IL 相同,则显然无法区分它们。 - Sriram Sakthivel

0

您被 C# 特定功能绊倒了,即方法参数上 refout 限定符之间的区别。这种区别对于实现 明确赋值 语言特性非常重要,可以很好地检测未分配变量的使用。

问题是,CLR 对此没有任何支持。它只能区分按值传递参数(ParameterAttributes.In)还是按引用传递参数(ParameterAttributes.Out)。例如,与 VB.NET 的 ByVal vs ByRef 关键字进行比较。

那么,C# 编译器如何知道在 另一个 程序集中编译的方法将参数作为 out 而不是 ref 传递?它无法从 ParameterAttributes 中找到答案,因为它根本没有编码这种区别。如果其他程序集的源代码是用 VB.NET 编写的,一种没有相同区别的语言,它也无法找到答案。

您可能已经通过尝试使用反射而发现了答案。 C#编译器自动在声明为out的任何参数上发出[Out]属性。如果缺少该属性,则会将其解释为ref

这是您可以通过反编译器(例如ildasm.exe)看到的内容:

.method public hidebysig newslot virtual final 
        instance void  OutModifier([out] int32& a) cil managed
// etc...

请注意[out]的存在。如果您将接口更改为ref,则会变成:
.method public hidebysig newslot virtual final 
        instance void  OutModifier(int32& a) cil managed

请注意,现在[out]已经消失了。 因此,在您的OutModifier()和OutAndOutAttributeModifier()实现方法之间根本没有区别,它们都编译为在参数上使用[Out]。正如反射所告诉你的那样。
如果您需要区别,当然您在这个例子中不需要,则需要使用另一种语言(例如VB.NET)编写方法。

这基本上也是我发现的。只要我发现它确实是一个“out”,那么我就可以只做“out int x”,而不必担心生成属性。只有在属性存在但参数不是“out”的情况下,我才会在我的代码中生成它(尽管看起来很奇怪,但就C#编译器而言,这是技术上正确的代码)。 - JasonBock
这种情况并不完全罕见,它通常发生在针对引用类型参数的PInvoke声明中,以及在Remoting中使用的引用类型。 - Hans Passant

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