下层调用者是否能够从使用C#4.0生成的程序集中的可选参数中受益?

时间:2021-10-03 10:50:40

Suppose I have an existing assembly, and some of the classes have overloaded methods, with default behavior or values assumed for some of those overloads. I think this is a pretty typical pattern;

假设我有一个现有的程序集,并且某些类具有重载方法,其中某些重载具有默认行为或值。我认为这是一个非常典型的模式;

Type2 _defaultValueForParam2 = foo;
Type3 _defaultValueForParam3 = bar;

public ReturnType TheMethod(Type1 param1)
{
   return TheMethod(param1, _defaultValueForParam2);
}
public ReturnType TheMethod(Type1 param1, Type2 param2)
{
   return TheMethod(param1, param2, _defaultValueForParam3);
}
public ReturnType TheMethod(Type1 param1, Type2 param2, Type3 param3)
{
   // actually implement the method here. 
}

And I understand that optional params in C# is supposed to let me consolidated that down to a single method. If I produce a method with some params marked optional, will it work with downlevel callers of the assembly?

我知道C#中的可选参数应该让我将其整合到一个方法中。如果我使用标记为可选的某些参数生成一个方法,它是否可以与程序集的下层调用程序一起使用?


EDIT: By "work" I mean, a downlevel caller, an app compiled with the C# 2.0 or 3.5 compiler, will be able to invoke the method with one, two or three params, just as if I had used overloads, and the downlevel compiler won't complain.

编辑:通过“工作”我的意思是,一个下层调用者,一个用C#2.0或3.5编译器编译的应用程序,将能够调用带有一个,两个或三个参数的方法,就像我使用了重载和下层一样编译器不会抱怨。

I do want to refactor and eliminate all the overloads in my library, but I don't want to force the downlevel callers using the refactored library to provide every parameter.

我想重构并消除我的库中的所有重载,但我不想强制下层调用者使用重构库来提供每个参数。

3 个解决方案

#1


I haven't read the docs on the new language standard, but I would assume that your pre-4.0 callers will have to pass all declared parameters, just as they do now. This is because of the way parameter-passing works.

我没有阅读有关新语言标准的文档,但我认为你的4.0之前的调用者必须传递所有声明的参数,就像现在一样。这是因为参数传递的工作方式。

When you call a method, the arguments are pushed onto the stack. If three 32-bit arguments are passed, then 12 bytes will be pushed onto the stack; if four 32-bit arguments are passed, then 16 bytes will be pushed onto the stack. The number of bytes pushed onto the stack is implicit in the call: the callee assumes that the correct number of arguments was passed.

调用方法时,参数将被压入堆栈。如果传递了三个32位参数,则将12个字节压入堆栈;如果传递了四个32位参数,则将16个字节压入堆栈。推送到堆栈的字节数隐含在调用中:被调用者假定传递了正确数量的参数。

So if a function takes four 32-bit parameters, it will look on the stack at the 16 bytes preceding the return address of the caller. If the caller has passed only 12 bytes, then the callee will read 4 bytes of whatever was already on the stack before the call was made. It has no way of knowing that all 16 expected bytes was not passed.

因此,如果一个函数需要四个32位参数,它将在调用者返回地址之前的16个字节处查看堆栈。如果调用者只传递了12个字节,那么被调用者将在调用之前读取堆栈中已有的4个字节。它无法知道所有16个预期字节都没有通过。

This is the way it works now. There's no changing that for existing compilers.

这是它现在的工作方式。现有的编译器没有改变。

To support optional parameters, one of two things has to happen:

要支持可选参数,必须执行以下两项操作之一:

  1. The caller can pass an additional value that explicitly tells the callee how many arguments (or bytes) were pushed onto the stack. The callee can then fill in the default values for any omitted parameters.
  2. 调用者可以传递一个额外的值,该值明确告诉被调用者将多少个参数(或字节)压入堆栈。然后被调用者可以填写任何省略的参数的默认值。

  3. The caller can continue passing all declared parameters, substituting default values (which would be read from the callee's metadata) for any optional parameters omitted in the code. The callee then reads all parameter values from the stack, just as it does now.
  4. 调用者可以继续传递所有声明的参数,用代码中省略的任何可选参数替换默认值(可以从被调用者的元数据中读取)。然后被调用者从堆栈中读取所有参数值,就像现在一样。

I suspect that it will be implemented as in (2) above. This is similar to how it's done in C++ (although C++, lacking metadata, requires that the default parameters be specified in the header file), is more efficient that option (1), as it is all done at compile time and doesn't require an additional value to pushed onto the stack, and is the most straightforward implementation. The drawback to option (2) is that, if the default values change, all callers must be recompiled, or else they will continue to pass the old defaults, since they've been compiled in as constants. This is similar to the way public constants work now. Note that option (1) does not suffer this drawback.

我怀疑它将按照上面的(2)实施。这类似于在C ++中完成的操作(尽管C ++,缺少元数据,要求在头文件中指定默认参数),比选项(1)更有效,因为它全部在编译时完成而不是需要一个额外的值来推入堆栈,这是最直接的实现。选项(2)的缺点是,如果默认值发生变化,则必须重新编译所有调用者,否则它们将继续传递旧的默认值,因为它们已作为常量编译。这类似于现在公共常量的工作方式。注意,选项(1)没有这个缺点。

Option (1) also does not support named parameter passing, whereby given a function declared like this:

选项(1)也不支持命名参数传递,因此给定一个声明如下的函数:

static void Foo(int a, int b = 0, int c = 0){}

it can be called like this:

它可以像这样调用:

Foo(1, c: 2);

Option (1) could be modified to allow for this, by making the extra hidden value a bitmap of omitted arguments, where each bit represents one optional parameter. This arbitrarily limits the number of optional parameters a function can accept, although given that this limitation would be at least 32, that may not be such a bad thing. It does make it exceedingly unlikely that this is the actual implementation, however.

通过使额外的隐藏值成为省略参数的位图,可以修改选项(1)以允许这一点,其中每个位代表一个可选参数。这任意地限制了函数可以接受的可选参数的数量,尽管这个限制至少为32,这可能不是一件坏事。但是,这确实使得这不太可能是实际的实现。

Given either implementation, the calling code must understand the mechanics of optional parameters in order to omit any arguments in the call. Additionally, with option (1), an extra hidden parameter must be passed, which older compilers would not even know about, unless it was added as a formal parameter in the metadata.

给定任一实现,调用代码必须理解可选参数的机制,以便省略调用中的任何参数。另外,使用选项(1),必须传递一个额外的隐藏参数,这是旧编译器甚至不知道的,除非它被添加为元数据中的形式参数。

#2


In c# 4.0, when an optional parameter is omitted, a default value for that parameter is substituted, to wit:

在c#4.0中,当省略可选参数时,将替换该参数的默认值,即:

public void SendMail(string toAddress, string bodyText, bool ccAdministrator = true, bool isBodyHtml = false)
{ 
    // Full implementation here   
}

For your downlevel callers, this means that if they use one of the variants that is missing parameters, c# will substitute the default value you have provided for the missing parameter. This article explains the process in greater detail.

对于您的下层调用者,这意味着如果他们使用缺少参数的变体之一,c#将替换您为缺失参数提供的默认值。本文将更详细地介绍该过程。

Your existing downlevel calls should all still work, but you will have to recompile your clients in c# 4.0.

您现有的下层调用应该仍然有效,但您必须在c#4.0中重新编译客户端。

#3


Well, I think that if you replace all 3 methods by a single method with optional parameters, the code that uses your library will still work, but will need to be recompiled.

好吧,我认为如果用一个带有可选参数的方法替换所有3个方法,那么使用你的库的代码仍然可以工作,但是需要重新编译。

#1


I haven't read the docs on the new language standard, but I would assume that your pre-4.0 callers will have to pass all declared parameters, just as they do now. This is because of the way parameter-passing works.

我没有阅读有关新语言标准的文档,但我认为你的4.0之前的调用者必须传递所有声明的参数,就像现在一样。这是因为参数传递的工作方式。

When you call a method, the arguments are pushed onto the stack. If three 32-bit arguments are passed, then 12 bytes will be pushed onto the stack; if four 32-bit arguments are passed, then 16 bytes will be pushed onto the stack. The number of bytes pushed onto the stack is implicit in the call: the callee assumes that the correct number of arguments was passed.

调用方法时,参数将被压入堆栈。如果传递了三个32位参数,则将12个字节压入堆栈;如果传递了四个32位参数,则将16个字节压入堆栈。推送到堆栈的字节数隐含在调用中:被调用者假定传递了正确数量的参数。

So if a function takes four 32-bit parameters, it will look on the stack at the 16 bytes preceding the return address of the caller. If the caller has passed only 12 bytes, then the callee will read 4 bytes of whatever was already on the stack before the call was made. It has no way of knowing that all 16 expected bytes was not passed.

因此,如果一个函数需要四个32位参数,它将在调用者返回地址之前的16个字节处查看堆栈。如果调用者只传递了12个字节,那么被调用者将在调用之前读取堆栈中已有的4个字节。它无法知道所有16个预期字节都没有通过。

This is the way it works now. There's no changing that for existing compilers.

这是它现在的工作方式。现有的编译器没有改变。

To support optional parameters, one of two things has to happen:

要支持可选参数,必须执行以下两项操作之一:

  1. The caller can pass an additional value that explicitly tells the callee how many arguments (or bytes) were pushed onto the stack. The callee can then fill in the default values for any omitted parameters.
  2. 调用者可以传递一个额外的值,该值明确告诉被调用者将多少个参数(或字节)压入堆栈。然后被调用者可以填写任何省略的参数的默认值。

  3. The caller can continue passing all declared parameters, substituting default values (which would be read from the callee's metadata) for any optional parameters omitted in the code. The callee then reads all parameter values from the stack, just as it does now.
  4. 调用者可以继续传递所有声明的参数,用代码中省略的任何可选参数替换默认值(可以从被调用者的元数据中读取)。然后被调用者从堆栈中读取所有参数值,就像现在一样。

I suspect that it will be implemented as in (2) above. This is similar to how it's done in C++ (although C++, lacking metadata, requires that the default parameters be specified in the header file), is more efficient that option (1), as it is all done at compile time and doesn't require an additional value to pushed onto the stack, and is the most straightforward implementation. The drawback to option (2) is that, if the default values change, all callers must be recompiled, or else they will continue to pass the old defaults, since they've been compiled in as constants. This is similar to the way public constants work now. Note that option (1) does not suffer this drawback.

我怀疑它将按照上面的(2)实施。这类似于在C ++中完成的操作(尽管C ++,缺少元数据,要求在头文件中指定默认参数),比选项(1)更有效,因为它全部在编译时完成而不是需要一个额外的值来推入堆栈,这是最直接的实现。选项(2)的缺点是,如果默认值发生变化,则必须重新编译所有调用者,否则它们将继续传递旧的默认值,因为它们已作为常量编译。这类似于现在公共常量的工作方式。注意,选项(1)没有这个缺点。

Option (1) also does not support named parameter passing, whereby given a function declared like this:

选项(1)也不支持命名参数传递,因此给定一个声明如下的函数:

static void Foo(int a, int b = 0, int c = 0){}

it can be called like this:

它可以像这样调用:

Foo(1, c: 2);

Option (1) could be modified to allow for this, by making the extra hidden value a bitmap of omitted arguments, where each bit represents one optional parameter. This arbitrarily limits the number of optional parameters a function can accept, although given that this limitation would be at least 32, that may not be such a bad thing. It does make it exceedingly unlikely that this is the actual implementation, however.

通过使额外的隐藏值成为省略参数的位图,可以修改选项(1)以允许这一点,其中每个位代表一个可选参数。这任意地限制了函数可以接受的可选参数的数量,尽管这个限制至少为32,这可能不是一件坏事。但是,这确实使得这不太可能是实际的实现。

Given either implementation, the calling code must understand the mechanics of optional parameters in order to omit any arguments in the call. Additionally, with option (1), an extra hidden parameter must be passed, which older compilers would not even know about, unless it was added as a formal parameter in the metadata.

给定任一实现,调用代码必须理解可选参数的机制,以便省略调用中的任何参数。另外,使用选项(1),必须传递一个额外的隐藏参数,这是旧编译器甚至不知道的,除非它被添加为元数据中的形式参数。

#2


In c# 4.0, when an optional parameter is omitted, a default value for that parameter is substituted, to wit:

在c#4.0中,当省略可选参数时,将替换该参数的默认值,即:

public void SendMail(string toAddress, string bodyText, bool ccAdministrator = true, bool isBodyHtml = false)
{ 
    // Full implementation here   
}

For your downlevel callers, this means that if they use one of the variants that is missing parameters, c# will substitute the default value you have provided for the missing parameter. This article explains the process in greater detail.

对于您的下层调用者,这意味着如果他们使用缺少参数的变体之一,c#将替换您为缺失参数提供的默认值。本文将更详细地介绍该过程。

Your existing downlevel calls should all still work, but you will have to recompile your clients in c# 4.0.

您现有的下层调用应该仍然有效,但您必须在c#4.0中重新编译客户端。

#3


Well, I think that if you replace all 3 methods by a single method with optional parameters, the code that uses your library will still work, but will need to be recompiled.

好吧,我认为如果用一个带有可选参数的方法替换所有3个方法,那么使用你的库的代码仍然可以工作,但是需要重新编译。