Alternating params - c#

Is there a kind of alternating params for method parameters?
I like the keyword params. But sometimes I need two parameters to be params.
I want to call a method like so:
Method(1, "a", 2, "b", 3, "c")
where 1, 2 and 3 are keys and "a", "b" and "c" are assigned values.
If I try to define the method parameters I would intuitively try to use params for two parameters like so:
void Method(params int[] i, string[] s)
Compiler would add every parameter at odd positions to the first parameter and every parameter at even positions to the second parameter.
But (as you know) params is only possible for last parameter.
Of course I could create a parameter class (e.g. KeyValue) and use it so:
Method(new[] {new KeyValue(1, "a"), new KeyValue(2, "b"), new KeyValue(3, "c")})
But that is too much code imo.
Is there any shorter notation?
Edit: Just now I found a good answer to another question: It suggests to inherit from List and to overload the Add method so that the new List can be initialized by this way:
new KeyValueList<int, string>{{ 1, "a" }, { 2, "b" }, { 3, "c" }}
Method definition would be:
void Method(KeyValueList<int, string> list)
Call would be:
Method(new KeyValueList<int, string>{{ 1, "a" }, { 2, "b" }, { 3, "c" }})

There is no "alternating params" notation as you described.
You can only have one params parameter and it must be last - if you want to have different types as params parameters you can use object as the array type.
Consider passing in a list made of a custom type that retains the meaning of these items.
public class MyType
{
public int MyNum { get; set; }
public string MyStr { get; set; }
}
Method(List<MyType> myList);

You could do this via params object[] keysAndValues and sort it out yourself, but... its a bit icky, what with all the boxing/unboxing that would go on.

Just giving an updated answer for today's developers looking for a solution...
Old School Answer:
You could create a class or struct that includes the parameters of the type you need...
struct Struct {
public int Integer {get; set;}
public string Text {get; set;}
public Struct(int integer, string text) {
Integer = integer;
Text = text;
}
}
... and then define your function to accept it as the params array...
void Method(params Struct[] structs)
... and call it like...
Method(new Struct(1, "A"), new Struct(2, "B"), new Struct(3, "C"));
New School Answer:
Or, with the latest C#, you could simply use ValueTuples...
Method(params (int, string)[] valueTuples)
... and call it like...
Method((1, "A"), (2, "B"), (3, "C"))

Related

How to supply two arrays as DataRow parameters?

I am trying to write a unit test that will compare two arrays. I have defined the unit test as so:
[DataTestMethod]
[DataRow(
new[] { "COM3", "COM1", "COM2" },
new[] { "COM1", "COM2", "COM3" }
)]
...
public void TestCOMPortSorting(string[] unorderedPorts, string[] expectedOrderedPorts)
However, my IDE throws the following error:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
I have tried using external variables, defining the arrays as new string[], creating a single array with these arrays, all with no luck.
How can I use these two arrays as parameters for my unit test?
For such complex data, change to using the DynamicData attribute
This attribute allows to get the values of the parameters from a method or a property. The method or the property must return an IEnumerable<object[]>. Each row corresponds to the values of a test.
[DataTestMethod]
[DynamicData(nameof(TestDataMethod), DynamicDataSourceType.Method)]
public void TestCOMPortSorting(string[] unorderedPorts, string[] expectedOrderedPorts) {
//...
}
static IEnumerable<object[]> TestDataMethod() {
return new[] {
new []{ new[] { "COM3", "COM1", "COM2" }, new[] { "COM1", "COM2", "COM3" } } //a data row
};
}
Reference MSTest v2: Data tests
DataRowAttribute can handle arrays of simple types. The issue here is that DataRowAttribute has the following overload for more than one parameters: DataRowAttribute(object data1, params object[] moreData).
In your expression, I think C# takes the second string array as the object[] moreData and it doesn't like it. If you specify the params object[] argument explicitly it will take the second string array as expected.
[DataTestMethod]
[DataRow(
new[] { "COM3", "COM1", "COM2" },
new object[] { new[] { "COM1", "COM2", "COM3" } }
)]
...
public void TestCOMPortSorting(string[] unorderedPorts, string[] expectedOrderedPorts)
Note that if you have any other 3rd argument it works without the workaround.

Collection Initializers in C#

In Java, I can create an List and immediately populate it using a static initializer. Something like this:
List &ltString&gt list = new ArrayList&ltString&gt()
{{
Add("a");
Add("b");
Add("c");
}}
Which is convenient, because I can create the list on the fly, and pass it as an argument into a function. Something like this:
printList(new ArrayList&ltString&gt()
{{
Add("a");
Add("b");
Add("c");
}});
I am new to C# and trying to figure out how to do this, but am coming up empty. Is this possible in C#? And if so, how can it be done?
You can use a collection initializer:
new List<string> { "a", "b", "c" }
This compiles to a sequence of calls to the Add method.
If the Add method takes multiple arguments (eg, a dictionary), you'll need to wrap each call in a separate pair of braces:
new Dictionary<string, Exception> {
{ "a", new InvalidProgramException() },
{ "b", null },
{ "c", new BadImageFormatException() }
}
Since C# 3.0 you can do it as well:
List <String> list = new List<String>
{
"a", "b", "c"
};
MSDN, Collection Initializers
Collection initializers let you specify one or more element
intializers when you initialize a collection class that implements
IEnumerable. The element initializers can be a simple value, an
expression or an object initializer. By using a collection initializer
you do not have to specify multiple calls to the Add method of the
class in your source code; the compiler adds the calls.
EDIT: Answer to comment regarding dictionary
IDictionary<string, string> map = new Dictionary<string, string>
{
{ "Key0", "Value0" },
{ "Key1", "Value1" }
};
Yes, it's described on MSDN here

Pass array of parameters to a web method

I would like to pass some parameters to a web method as an array. The web method's signature does not have the params keyword.
I have a variable number of parameters (as the web method accepts) so I cannot put the array into n single variables.
How can this be done?
params is just syntactic sugar, why not just do something like this:
var myWebService = new MyWebService();
myWebService.MyMethod(new string[] { "one", "two", "three" });
The method signature on the web service side would just be:
public void MyMethod(string[] values);
If you post your web method maybe I can provide a better answer.
EDIT
If you can't modify the web method signature, then I would use an extension method to wrap the difficult to call web service. For example, if our web service proxy class looks like:
public class MyWebService
{
public bool MyMethod(string a1, string a2, string a3, string a4, string a5,
string a6, string a7, string a8, string a9, string a10)
{
//Do something
return false;
}
}
Then you could create an extension method that accepts the string array as params and makes the call to MyWebService.
public static class MyExtensionMethods
{
public static bool MyMethod(this MyWebService svc, params string[] a)
{
//The code below assumes you can pass in null if the parameter
//is not specified. If you have to pass in string.Empty or something
//similar then initialize all elements in the p array before doing
//the CopyTo
if(a.Length > 10)
throw new ArgumentException("Cannot pass more than 10 parameters.");
var p = new string[10];
a.CopyTo(p, 0);
return svc.MyMethod(p[0], p[1], p[2], p[3], p[4], p[5],
p[6], p[7], p[8], p[9]);
}
}
You could then call your web service using the extension method you created (just be sure to add a using statment for the namespace where you declared you extension method):
var svc = new MyWebService();
svc.MyMethod("this", "is", "a", "test");
Why don't you use..an array?
[WebMethod]
public string Foo(string[] values)
{
return string.Join(",", values);
}
Ignore the fact that it is a web method for a moment. Ultimately, it is a method. And like all methods, is either defined to take parameters or not. If it is not defined to take parameters, then you can't pass parameters to it, can you? Not unless you have access to the source code and are able to chance it's definition.
If it is defined to take parameters, then the question is, is it defined to take array parameters or not? If not, then you can't pass parameters to it (not unless you can change it so that it can.)

How will be object[] params interpreted if they are passed into another procedure with object[] params?

If we have two procedures:
public void MyFirstParamsFunction(params object[] items)
{
//do something
MySecondParamsFunction(items, "test string", 31415)
}
public void MySecondParamsFunction(params object[] items)
{
//do something
}
How will second procedure interpret the items from first? As sole objects, or as one object, that is object[]?
object[] items will be interpreted as one object[] parameter.
This call:
MySecondParamsFunction(items, "test string", 31415);
Will expand to this:
MySecondParamsFunction(new object[] { items, "test string", 31415 });
So you'll have 3 items in your params array for the call. Your original items array will be crammed into the first item in the new array.
If you want to just have a flattened parameter list going into the second method, you can append the new items to the old array using something like this:
MySecondParamsFunction(
items.Concat(new object[] { "test string", 31415 }).ToArray());
Or maybe with a nicer extension method:
MySecondParamsFunction(items.Append("test string", 31415));
// ...
public static class ArrayExtensions {
public static T[] Append<T>(this T[] self, params T[] items) {
return self.Concat(items).ToArray();
}
}
If you check it yourself you would have noticed that it is seen as a single object.
So in MySecondParamsFunction the items parameter will have length of 3 in this case.
The params keyword is just a bit of syntactic sugar.
Calling MyFirstParamsFunction(1, 2, 3) is the same as MyFirstParamsFunction(new object[] { 1, 2, 3 }). The compiler injects the code to create the array behind your back.

Using key-value pairs as parameters

Simple. If I use:
public void Add(params int[] values)
Then I can use this as:
Add(1, 2, 3, 4);
But now I'm dealing with key-value pairs! I have a KeyValue class to link an integer to a string value. So I start with:
public void Add(params KeyValue[] values)
But I can't use this:
Add(1, "A", 2, "B", 3, "C", 4, "D");
Instead, I'm forced to use:
Add(new KeyValue(1, "A"), new KeyValue(2, "B"), new KeyValue(3, "C"), new KeyValue(4, "D"));
Ewww... Already I dislike this...
So, right now I use the Add function without the params modifier and just pass a pre-defined array to this function. Since it's just used for a quick initialization for a test, I'm not too much troubled about needing this additional code, although I want to keep the code simple to read. I would love to know a trick to use the method I can't use but is there any way to do this without using the "new KeyValue()" construction?
If you accepted an IDictionary<int,string>, you could presumably use (in C# 3.0, at least):
Add(new Dictionary<int,string> {
{1, "A"}, {2, "B"}, {3, "C"}, {4, "D"}
});
Any use?
Example Add:
static void Add(IDictionary<int, string> data) {
foreach (var pair in data) {
Console.WriteLine(pair.Key + " = " + pair.Value);
}
}
You can modify your current class design, but you will need to add generics and use the IEnumerable interface.
class KeyValue<TKey, TValue>
{
public KeyValue()
{
}
}
// 1. change: need to implement IEnumerable interface
class KeyValueList<TKey, TValue> : IEnumerable<TKey>
{
// 2. prerequisite: parameterless constructor needed
public KeyValueList()
{
// ...
}
// 3. need Add method to take advantage of
// so called "collection initializers"
public void Add(TKey key, TValue value)
{
// here you will need to initalize the
// KeyValue object and add it
}
// need to implement IEnumerable<TKey> here!
}
After these additions you can do the following:
new KeyValueList<int, string>() { { 1, "A" }, { 2, "B" } };
The compiler will use the IEnumerable interface and the Add method to populate the KeyValueList. Note that it works for C# 3.0.
If you are using this for tests, these changes are not worth it. It's quite an effort and you change quite a lot of production code for tests.
You could use something like the following with the obvious drawback that you loose strong typing.
public void Add(params Object[] inputs)
{
Int32 numberPairs = inputs.Length / 2;
KeyValue[] keyValues = new KeyValue[numberPairs];
for (Int32 i = 0; i < numberPairs; i++)
{
Int32 key = (Int32)inputs[2 * i];
String value = (String)inputs[2 * i + 1];
keyvalues[i] = new KeyValue(key, value);
}
// Call the overloaded method accepting KeyValue[].
this.Add(keyValues);
}
public void Add(params KeyValue[] values)
{
// Do work here.
}
You should of cause add some error handling if the arguments are of incorrect type. Not that smart, but it will work.

Categories

Resources