我如何在C#中使用params`out`?

时间:2009-12-16 04:39:25

标签: c# .net

我发现自己处于需要这个的情况

public static void Fill(this SomeClass c, params out object[] p)

并将其称为

c.Fill(out i, out i2, out sz, out i3, out sz2);

但是我收到错误error CS1611: The params parameter cannot be declared as ref or out

如何传递可变长度参数并使其可写?所有这些都是整数和字符串的混合

7 个答案:

答案 0 :(得分:19)

您无法将参数视为out(或ref同时使用params功能。它根本不起作用。您可以做的最好的事情是创建一个数组参数,使数组 out,声明一个数组变量并调用传递数组的方法,然后通过索引手动检查每个元素。

Foo(out object[] data) {...}
object[] result;
Foo(out result);
// look at result[0], result[1], result[2] etc

所以:你不能做你想要的。即使可以,ref / out 永远不会工作,除非数据类型之间存在完全匹配,因此它仍然< / em>必须是:

object o1, o2, o3, o4;
Foo(out o1, out o2, out o3, out o4);
// cast o1, o2, o3, o4

这仍然不是你想要的。

答案 1 :(得分:7)

此处out没有技术需求。这有效:

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];

您可以将您的值返回为Tuple
(如果您不使用.NET4.0,则定义您自己的元组类)

static Tuple<int, string> Fill()
{
    return new Tuple(42, "Hello World");
}

然后定义解包元组的扩展方法:

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;
    }
}

然后你可以这样写:

int i;
string sz;

foo.Fill().Unpack(out i, out sz);

答案 2 :(得分:2)

1)如果你可以避免在声明的变量中获取值,那么传递数组并填充它是最好的选择,如dtb的答案所示。

2)否则,您可以为变量设置一个简单的包装器。

public class Wrapper //or may be generic?
{
    public object Value { get; set; }

    public Wrapper(object value)
    {
        Value = value;
    }
}

现在你可以打电话了

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
    }
}

调用Value方法后,您必须处理Fill属性。

3)你可以利用封闭。像Ref<T>类那样实现如下所示:

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

很少有事情需要注意:

  1. 所有上述方法基本上都是ref方法,编译器不会简单地强制在控件离开之前在方法内部分配参数值,就像out这个问题一样,这是你的问题,但据我所知out在这里是不可能的。

  2. 我喜欢第一个,简单,传统。如果不可能,我的投票是第三种方法。

  3. 正如其他人所说,您只能传递与ref/out参数完全相同的类型。因此,如果您的方法按定义采用object类型的任意引用,则必须在本地将变量声明为object。在最后一种方法中,您可以通过将参数类型从Ref<T>更改为Ref<object>来使整个事物变得通用,但这意味着您的所有局部变量也应该是T

  4. 您可以使用字典结构来缓存Ref<T>,以避免重新编译相同的树。

  5. 可以使用相同的实现将属性和变量作为方法参数传递或通过引用返回值。

答案 3 :(得分:1)

正如其他人所说,你不能同时使用paramsout。您必须在呼叫站点构建一个数组。

这是因为params告诉编译器做同样的事情 - 从指定的参数构造一个数组。不幸的是,当编译器创建数组时,你没有得到它的引用;即使变量是用新数组编写的,也永远无法实现。

我猜你是asking for a thin metal ruler。你试图用这种机制解决什么问题?

答案 4 :(得分:0)

您可以通过参考传递数组。

编辑:

这当然会改变你的调用方法:

object[] array = new object[] { i, i2, sz, i3, sz2 };
c.Fill(ref array);

答案 5 :(得分:0)

我想我可能会回答你的问题;请考虑以下代码片段,主要的“InvokeMemberMethod”函数执行您要求的工作。我遇到了和你一样的问题并提出了这个解决方案:

注意:“isOutXX”参数指定前面的参数是否为“out”参数。

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);
    }

答案 6 :(得分:0)

我不认为最初的提问者会在11年后需要这个答案,但是当我寻找一个重叠的需求时我发现了这个老问题...并且仔细思考这个问题的答案使我意识到了为什么我的亲戚不会这样做。工作非常整齐。

对于这个问题,这意味着调用者需要将参数及其类型的数量传递给Fill(...)函数-否则它将如何与调用站点的类型进行匹配?

可以通过以下方式在调用站点上实现预期的语法:

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);
}