Parameter name aliases

Topics: C# Language Design
Jul 19, 2014 at 3:06 PM
Consider the following scenario:
public abstract class CompilerBase
{

  protected abstract string GetSwitches (string sourceFileName,
                                         string targetFileName);

  public async Task<CompilerResult> Compile (string sourceFileName,
                                             string targetFileName)
  {
     var scriptArgs = GetSwitches (sourceFileName, targetFileName);

     /*
         Eradicated for brevity:
         The rest of the method will never use either
         of those two parameters
     */
  }

}
Clearly, the derived classes are the ones to resolve the "context" or the "meanings" of those two parameters in overridden method GetArugument( .. )

In this situation, if one of the derived implementation is expecting configurationFileName instead of targetFileName (which cause the name ambiguity), we are supposed to overload it with third parameter (with or without default parameter; depending on if you are considering CA1026 rule).

In any case, it will always be second-parameter XOR third-parameter.

Another way to handle this is by creating public new CompilerResult Compile(string, string) with different parameter names that calls the base version, and misuses the base parameter (semantically speaking).

Please support aliasing via [Alias()] attribute for the parameter names for such situations. Also, a code analysis rule that the parameters are not being used in the base method as assigner or assignee.

Inspired by PowerShell's [Alias()] decorator

I can't think of any CA rule or Object Oriented design principles which would be violated by this enhancement.
Jul 23, 2014 at 3:51 AM
To make sure that I understand your proposal, first let me reiterate what I believe you are asking for.

Fundamentally, you argue, overloaded functions are tasked with accomplishing the same task when provided different sets of data. At times, this is easily done, because the type system makes it easy for you. For example:
public static void Member1(string first, A second){ /* something */ }
public static void Member1(string first, B second){ /* something */ }

Member1("value1", var1 as A);
Member1("value1", var2 As B);
However, unlike today, you wish to be able to distinguish functions based on parameter name like follows:
public static void Member1(string first, string second){ /* something */ }
public static void Member1(string first, string third){/* something */ }

Member1(first:"value1", second:"value2");
Member1(first:"value1", third:"value3");
You also propose (I think rightfully) that the following is too clunky:
public static void Member1(string first, string second = "", string third = "")
{
   var hasSecond = string.IsNullOrEmpty(second);
   var hasThird = string.IsNullOrEmpty(third);
   if ( (hasSecond && hasThird) || !(hasSecond || hasThird))
   {
      throw new ArgumentException("Bad combination of arguments");
   }
    /* something */
}
If what I have posted above seems to correctly summarize your proposal, then I respectfully disagree with the need for this feature. Consider the two following examples:

The current day solution to your problem:
public static void Member1second(string first, string second) { /* do something */ }
public static void Member1third(string first, string third) { /* do something */ }

Member1second("value1", "value2");
Member1third("value1", "value3");
Your proposed solution to the problem:
public static void Member1(string first, string second){ /* do something */ }
public static void Member1(string first, string third){ /* do something  */ }

Member1(first:"value1", second:"value2"); //Clearly called
Member1(first:"value1", third:"value3"); //Clearly called
Member1("value1", "value2"); //Ambiguously called
Member1("value1", "value3"); //Ambiguously called
First, in this proposed solution you do not specify how to handle situations where arguments are not specified by name. Next, even when you do call the function with named arguments, you aren't really saving yourself any typing, and you aren't resolving a situation where a programmer can reasonably off-load the work of selecting an overload call to the compiler.

As for adding an attribute, I'm not sure what problems you're solving with it, there was never any confusion on the compilers part about what method was being overloaded as they share a name.
Jan 15, 2015 at 2:04 AM
Edited Jan 15, 2015 at 2:05 AM
@JackRackham19, considering this is merely the request for "aliasing", multiple-names or nicknames, going by your example second and third means the exact same thing. There is no ambiguity. The user should not be able to use alias names in the function scope. In a large code base, when the method is called, then the user may want to use suitable alias for clarity of intent.

So we can say:
[Alias({second: [secondAndHalf, quarterToThree], first: [Initial]})] 
public static void Member1(string first, string second) { 
  // Here the user will always use the given names: first and second.
  // which means, the user cannot call the param by its nickname
}

private void Caller1() {
    Member1(secondAndHalf: "secondly, this is not a bad idea after all",
            Initial: "Initially it sounded moot... :)  ");
}


private void Caller2() {
    Member1(first: "1st",
            quarterToThree: "almost there");
}
Jan 15, 2015 at 2:30 PM
The only use case for this that you provided (in your first post) is when you're misusing a single parameter for multiple purposes.

If you want a collection of strings, whose meaning will be decided by the callee, use a collection of strings.

If you want a more complicated combination of parameters, possibly depending on the callee, either you shouldn't use inheritance or use parameter object. That way, using incorrect parameter name will be an error. With your proposal, if the callee expects configurationFileName, but I give it targetFileName, then there is no way to recognize it as an error.

So I think your proposal seems to be useful only in code that doesn't follow best practices and even then, it makes it too easy to write buggy code.
Jan 15, 2015 at 4:11 PM
@svick, thanks for sharing your thoughts.

It is not confined to the inheritance scenarios. There is no need for compiler to check the error because either way the param type is same and we certainly know that the code in the method is reusable. It is just the declaration of caller's intent to the reviewer, that what human meaning it is trying to assert.

Since this repo has recently moved to GitHub, I have opened this discussion there: https://github.com/dotnet/roslyn/issues/7. I tried to keep the example simple. :)
Jan 15, 2015 at 5:52 PM
svick wrote:
If you want a more complicated combination of parameters, possibly depending on the callee, either you shouldn't use inheritance or use parameter object. That way, using incorrect parameter name will be an error. With your proposal, if the callee expects configurationFileName, but I give it targetFileName, then there is no way to recognize it as an error.
The .NET languages have some abilities which may make the "parameter-object" approach somewhat nicer than it would be in Java. In particular, it's possible in .NET to have multiple structure types with widening conversions to a common type; if the structures and the conversions are sufficiently simple (e.g. each structure just contains a couple of String references, and the conversion methods simply load or copy them), then if one has using-imported a suitable alias for the action types, invoking a function
void Fnorble(FnorbleActionParams param)
using a syntax like:
Fnorble(fa.CreateFile("George"));
Fnorble(fa.ShowMessage("Hey there"));
my be no more expensive than invoking a method that takes two strings via:
Fnorble("CreateFile", "George");
Fnorble("ShowMessage", "Hey there");
but the fa.CreateFile and fa.ShowMessage factory methods could have different parameter names. Note that while one could reasonably argue that separate messages should be used for CreateFile and ShowMessage, there can be advantages to using fewer methods, especially when using interfaces. If a type implements an interface with ten methods and wants nine of them to simply chain to another implementation, it must actually implement all ten of them. If, however, the interface includes one method using an action-designator parameter, then an implementation doesn't need to have any code for actions it's not interested in--it can simply pass them along directly.
Jan 15, 2015 at 5:58 PM
Edited Jan 15, 2015 at 6:00 PM
svick wrote:
So I think your proposal seems to be useful only in code that doesn't follow best practices and even then, it makes it too easy to write buggy code.
One scenario where I can see it might be helpful would be in cases where one of the methods in a shipped version of a class gave a less-than-ideal name to one of its parameters. If e.g. the first version of a class had a property called "Wierdness", there would be no problem with a later version of the class adding a property called Weirdness, and then having Wierdness chain to that (marking the latter so as not to appear in Intellisense and perhaps also marking it deprecated as well). If the constructor had a parameter wierdness, such a parameter should probably also be renamed to weirdness, but such a change would potentially be breaking unless wierdness could be aliased to weirdness.

The existence of the Wierdness alias wouldn't be motivated by a desire to write good code, but rather a desire to avoid breaking code which used a name that should never have been used in the first place. Ideally, such aliasing wouldn't just be limited to parameters but also to fields and virtual functions (code which doesn't override virtual functions can get by just fine with a non-virtual method that chains to a virtual one, but code which overrides a virtual function has to know the correct function to override).
Jan 15, 2015 at 7:21 PM
TheDeeds wrote:
It is not confined to the inheritance scenarios. There is no need for compiler to check the error because either way the param type is same and we certainly know that the code in the method is reusable. It is just the declaration of caller's intent to the reviewer, that what human meaning it is trying to assert.
What I'm trying to say is that this makes it too easy to write buggy code, because the intent not the type is incorrect.

So, if I write Compile("source.cs", configurationFileName: "configuration.json"), but the version of Compile() I'm using is expecting targetFileName, not configurationFileName, then it's likely that the output will be written into configuration.json, which is certainly a bug.

For example, if you used parameter object, then Compile(new CompilerParameters { SourceFileName = "source.cs", ConfigurationFileName = "configuration.json" }) is more verbose, but it can throw an exception complaining about TargetFileName being null.
Jan 15, 2015 at 7:23 PM
@supercat: Yeah, fixing typos (or the like) seems like a good use of this.
Jan 15, 2015 at 7:53 PM
@svick,

Lets say if there was a bug in this common utility method, that is; it is incapable of differentiating from the param value, then regardless of Alias decorator, it will throw, would it not? That is why I specifically mentioned in the code comment that inside the function scope, the param cannot be called by its alias owing to the reason; alias is to be used by the callers not the implementation itself.

We were using it with node.js compilers class, where these parameters are actually (blindly) appended to the arguments list to be passed to the node process (which then invokes the suitable npm package based on the arguments). Since there happened to be polymorphism before calling this argument factory method, it will always construct the correct argument list (indirectly) based on the (MEF) content-type. Otherwise nodejs would throw a pretty JSON error, which we parse, cast and display in VS' Error List via EnvDTE. So this is how it was safe.

Having said that, if we think about it, there may be more than one use-case here. Hint: PowerShell provides a similar decorator for its functions.
Jan 15, 2015 at 8:03 PM
svick wrote:
@supercat: Yeah, fixing typos (or the like) seems like a good use of this.
Even that seems like overkill. Parameter names aren't really a part of the contract and the only time that they matter is if you're explicitly using named/optional arguments. If it started there would it extend to adding alias support and metadata to every aspect of any contract in the language? Beyond that it seems to only serve the purpose of refactoring by avoiding refactoring which only leads to code clutter and increased tech debt. Either the names matter and are a part of the contract, in which case changing them should be a painful breaking change, or they don't, in which case it doesn't matter what they're named.

Also, the attribute syntax proposed is completely outside of what attributes could support today. At best you'd need something like the following:
[Alias("first", "initial")]
[Alias("second", "secondAndHalf")]
[Alias("second", "quarterToThree")]
public static void Member1(string first, string second) { 
}