区分反射中的 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# 特定的功能绊倒了,即方法参数上 ref
和 out
限定符之间的区别。进行区分对于实现 definite assignment 语言功能很重要,这对于检测未分配变量的使用非常有用。
问题是,CLR 对此不提供任何支持。它只能区分按值(ParameterAttributes.In)或按引用(ParameterAttributes.Out)传递参数。例如,比较 VB.NET ByVal
与 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。
如果我这样写:
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# 特定的功能绊倒了,即方法参数上 ref
和 out
限定符之间的区别。进行区分对于实现 definite assignment 语言功能很重要,这对于检测未分配变量的使用非常有用。
问题是,CLR 对此不提供任何支持。它只能区分按值(ParameterAttributes.In)或按引用(ParameterAttributes.Out)传递参数。例如,比较 VB.NET ByVal
与 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。