Reputation:
I find myself in the situation requiring this
public static void Fill(this SomeClass c, params out object[] p)
and calling it as
c.Fill(out i, out i2, out sz, out i3, out sz2);
However i get the error error CS1611: The params parameter cannot be declared as ref or out
How can i pass in variable length arguments and make them writeable? All of these are a mixture of ints and strings
Upvotes: 14
Views: 8290
Reputation: 45445
As others have said, you can't use params
and out
together. You have to construct an array at the call site.
This is because params
tells the compiler to do the same thing - construct an array from the specified arguments. Unfortunately, when the compiler creates the array, you don't get a reference to it; even if the variable is written with a new array, you can never get to it.
I would guess you are asking for a thin metal ruler. What problem are you trying to solve with this mechanism?
Upvotes: 1
Reputation: 612
I don't imagine the original questioner needs this answer 11 years later, but I found this old question when searching for an overlapping requirement... and thinking through the answer to this question made me realise why my related one wouldn't work very neatly.
For this question, it was implied that the caller needs to communicate the number of parameters and their types to the Fill(...)
function - how else does it match up the types to the calling site?
The intended syntax at the calling site could be achieved like this:
public class SomeClass { }
public static void Fill(this SomeClass c, Type[] outputTypes, out object[] p)
{
p = new object[outputTypes.Length];
// TODO: implementation to fill array with values of the corresponding types.
}
// this overload can be removed if "fill" of an empty array is meaningless.
public static void Fill(this SomeClass c)
{
c.Fill(new Type[0], out _);
}
public static void Fill<T>(this SomeClass c, out T r1)
{
c.Fill(new[] { typeof(T) }, out var p);
r1 = (T)p[0];
}
public static void Fill<T1, T2>(this SomeClass c, out T1 r1, out T2 r2)
{
c.Fill(new[] { typeof(T1), typeof(T2) }, out var p);
r1 = (T1)p[0];
r2 = (T2)p[1];
}
// ... extend as required depending on maximum number of out parameters that might be needed
// in particular the 5-parameter version is included in this sample to make OP's sample code line work.
public static void Fill<T1, T2, T3, T4, T5>(this SomeClass c, out T1 r1, out T2 r2, out T3 r3, out T4 r4, out T5 r5)
{
c.Fill(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, out var p);
r1 = (T1)p[0];
r2 = (T2)p[1];
r3 = (T3)p[2];
r4 = (T4)p[3];
r5 = (T5)p[4];
}
public static void someFunction()
{
SomeClass c = new SomeClass();
int i, i2, i3;
string sz, sz2;
// the line below is exactly as shown in question.
c.Fill(out i, out i2, out sz, out i3, out sz2);
}
Upvotes: 0
Reputation: 73173
1) If you can avoid the need to get the values in declared variables, then passing the array and populating it is the best option, as shown by dtb's answer.
2) Otherwise you can have a simple wrapper for your variable.
public class Wrapper //or may be generic?
{
public object Value { get; set; }
public Wrapper(object value)
{
Value = value;
}
}
Now you can call
var i = new Wrapper(0), i2 = new Wrapper(0), i3 = new Wrapper(0);
c.Fill(i, i2, i3);
i.Value //your value here
public static void Fill(this SomeClass c, params Wrapper[] p)
{
for (int i = 0; i < p.Length; i++)
{
p[i].Value = 1; //assigning
}
}
You will have to deal with Value
property after calling Fill
method.
3) You can make use of closure. Something like the Ref<T>
class implemented as shown:
public static class Ref
{
public static Ref<T>[] Create<T>(params Expression<Func<T>>[] getters)
{
return getters.Select(Create).ToArray();
}
public static Ref<T> Create<T>(Expression<Func<T>> getter)
{
return new Ref<T>(getter);
}
}
public sealed class Ref<T>
{
readonly Func<T> getter;
readonly Action<T> setter;
public Ref(Expression<Func<T>> getter)
{
var output = getter.Body;
var input = Expression.Parameter(output.Type); //or hardcode typeof(T)
var assign = Expression.Assign(output, input);
var setter = Expression.Lambda<Action<T>>(assign, input);
this.getter = getter.Compile();
this.setter = setter.Compile();
}
public T Value
{
get { return getter(); }
set { setter(value); }
}
}
public static void Fill(this SomeClass c, params Ref<object>[] p)
//assign inside
object i = 0, i2 = 0, i3 = 0;
c.Fill(Ref.Create(() => i, () => i2, () => i3));
//i, i2 etc changed
Few things to note:
All the above approaches are basically ref
approaches, compiler doesn't simply force assigning value of parameters inside the method before the control leaves as in the case of out
which is your question, but as far as I know out
is not possible here.
I like the first one, simple, and conventional. If not possible my vote is for 3rd approach.
As others have talked about, you can only pass the exact same type as ref/out
parameters. So if your method by definition takes arbitrary references of object
type, you have to declare even your variables as object
locally. In the last approach, you can make the whole thing generic like by changing parameter type to Ref<T>
from Ref<object>
but that means your all local variables should also be one T
.
You can use a dictionary structure to cache Ref<T>
to avoid recompiling same trees.
The same implementation can be used to pass properties and variables as method arguments or return values by reference.
Upvotes: 2
Reputation: 278
I think I might have an answer to your question; Consider the following code snippet, with the main "InvokeMemberMethod" function doing the job you ask for. I encountered the same problem as you and came up with this solution:
Note: the "isOutXX" parameter specifies if the preceeding parameter is an "out" parameter.
static object InvokeMemberMethod(object currentObject, string methodName, int argCount,
ref object arg1, bool isOut1,
ref object arg2, bool isOut2,
ref object arg3, bool isOut3,
ref object arg4, bool isOut4,
ref object arg5, bool isOut5,
ref object arg6, bool isOut6)
{
if (string.IsNullOrEmpty(methodName))
{
throw new ArgumentNullException("methodName");
}
if (currentObject == null)
{
throw new ArgumentNullException("currentObject");
}
Type[] argTypes = null;
object[] args = null;
if (argCount > 0)
{
argTypes = new Type[argCount];
args = new object[argCount];
argTypes[0] = arg1.GetType();
if (isOut1)
{
argTypes[0] = arg1.GetType().MakeByRefType();
}
args[0] = arg1;
if (argCount == 2)
{
argTypes[1] = arg2.GetType();
if (isOut2)
{
argTypes[1] = arg2.GetType().MakeByRefType();
}
args[1] = arg2;
}
if (argCount == 3)
{
argTypes[2] = arg3.GetType();
if (isOut3)
{
argTypes[2] = arg3.GetType().MakeByRefType();
}
args[2] = arg3;
}
if (argCount == 4)
{
argTypes[3] = arg4.GetType();
if (isOut4)
{
argTypes[3] = arg4.GetType().MakeByRefType();
}
args[3] = arg4;
}
if (argCount == 5)
{
argTypes[4] = arg5.GetType();
if (isOut5)
{
argTypes[4] = arg5.GetType().MakeByRefType();
}
args[4] = arg5;
}
if (argCount == 6)
{
argTypes[5] = arg6.GetType();
if (isOut6)
{
argTypes[5] = arg6.GetType().MakeByRefType();
}
args[5] = arg6;
}
}
MethodInfo methodInfo = currentObject.GetType().GetMethod(methodName, argTypes);
int retryCount = 0;
object ret = null;
bool success = false;
do
{
try
{
//if (methodInfo is MethodInfo)
{
Type targetType = currentObject.GetType();
ParameterInfo[] info = methodInfo.GetParameters();
ParameterModifier[] modifier = new ParameterModifier[] { new ParameterModifier(info.Length) };
int i = 0;
foreach (ParameterInfo paramInfo in info)
{
if (paramInfo.IsOut)
{
modifier[0][i] = true;
}
i++;
}
ret = targetType.InvokeMember(methodName, BindingFlags.InvokeMethod, null, currentObject, args,
modifier, null, null);
//ret = ((MethodInfo)methodInfo).Invoke(currentObject, args,);
success = true;
}
//else
{
// log error
}
}
catch (TimeoutException ex)
{
}
catch (TargetInvocationException ex)
{
throw;
}
retryCount++;
} while (!success && retryCount <= 1);
if (argCount > 0)
{
if (isOut1)
{
arg1 = args[0];
}
if (argCount == 2)
{
if (isOut2)
{
arg2 = args[1];
}
}
if (argCount == 3)
{
if (isOut3)
{
arg3 = args[2];
}
}
if (argCount == 4)
{
if (isOut4)
{
arg4 = args[3];
}
}
if (argCount == 5)
{
if (isOut5)
{
arg5 = args[4];
}
}
if (argCount == 6)
{
if (isOut6)
{
arg6 = args[5];
}
}
}
return ret;
}
public int OutTest(int x, int y)
{
return x + y;
}
public int OutTest(int x, out int y)
{
y = x + 1;
return x+2;
}
static void Main(string[] args)
{
object x = 1, y = 0, z = 0;
Program p = new Program();
InvokeMemberMethod(p, "OutTest", 2,
ref x, false,
ref y, true,
ref z, false,
ref z, false,
ref z, false,
ref z, false);
}
Upvotes: 0
Reputation: 217293
There is no technical need for out
here. This works:
void Fill(object[] p)
{
p[0] = 1;
p[1] = 42;
p[2] = "Hello";
p[3] = -1;
p[4] = "World";
}
object[] p = new object[5];
foo.Fill(p);
i = (int)p[0];
i2 = (int)p[1];
sz = (string)p[2];
i3 = (int)p[3];
sz2 = (string)p[4];
You could return your values as Tuple:
(define your own tuple class if you're not using .NET4.0)
static Tuple<int, string> Fill()
{
return new Tuple(42, "Hello World");
}
and then define extension methods to unpack tuples:
public static class TupleExtensions
{
public static void Unpack<T1, T2>(
this Tuple<T1, T2> tuple,
out T1 item1,
out T2 item2)
{
item1 = tuple.Item1;
item2 = tuple.Item2;
}
}
Then you can write this:
int i;
string sz;
foo.Fill().Unpack(out i, out sz);
Upvotes: 7
Reputation:
You could pass an array by ref.
Edit:
This would of course change your calling method:
object[] array = new object[] { i, i2, sz, i3, sz2 };
c.Fill(ref array);
Upvotes: 0
Reputation: 1062770
You can't have it treat the arguments as out
(or ref
) and make use of the params
feature at the same time. It simply doesn't work. The best you can do is to create an array parameter, make the array out
, declare an array variable and call the method passing the array, then inspect each element manually by index.
Foo(out object[] data) {...}
object[] result;
Foo(out result);
// look at result[0], result[1], result[2] etc
So: you cannot do what you want. Even if you could, ref
/ out
never work unless there is an exact match between data type, so it would still have to be:
object o1, o2, o3, o4;
Foo(out o1, out o2, out o3, out o4);
// cast o1, o2, o3, o4
Which still isn't what you want.
Upvotes: 20