区分反射中的 OutAttribute 和 out 修饰符

Differentiating Between OutAttribute and out Modifier in Reflection

如果我这样写:

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

VS会这样实现:

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 是否存在……但我不知道如何(通过反射)。在这两种情况下,任何获取自定义属性信息的方法(GetCustomAttributes()、GetCustomAttributeData() 等)都会报告所有接口方法都存在 OutAttribute。这不是当前项目中现有代码的情况 - 如果我引用具有这些接口的程序集,VS 仍会生成上面显示的相同代码。

那么,我如何区分 "out" 参数和显式添加了“[Out]”属性的参数?

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

IOM是输出参数的正确用法。 IOAM 只是一个失败。

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

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

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

这是因为 out 参数必须通过引用传递。在您的示例 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.

来完成

您被 C# 特定的功能绊倒了,即方法参数上 refout 限定符之间的区别。进行区分对于实现 definite assignment 语言功能很重要,这对于检测未分配变量的使用非常有用。

问题是,CLR 对此不提供任何支持。它只能区分按值(ParameterAttributes.In)或按引用(ParameterAttributes.Out)传递参数。例如,比较 VB.NET ByValByRef 关键字。

那么 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。