【问题标题】:How can i make params `out` in C#?如何在 C# 中使参数“out”?
【发布时间】:2009-12-16 04:39:24
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# .net


    【解决方案1】:

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

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

    所以:你不能做你想要的。即使可以,ref / out 永远 工作,除非数据类型之间存在 exact 匹配,所以它会 仍然成为:

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

    这仍然不是你想要的。

    【讨论】:

    • 此外,您可以定义几个重载,这些重载可以连续获取更多的参数,直到某一点。例如,5 个重载分别具有 1、2、3、4 和 5 个输出参数。这应该涵盖许多(如果不是大多数)用例,然后对于调用者需要 很多 个输出的包罗万象的情况,请使用此处描述的整个数组方法。
    【解决方案2】:

    这里没有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);
    

    【讨论】:

    • 这就是问题所在。关键是不要这样做并且像调用一个简单的函数而不是可怕的一行类型转换或每个语句的函数调用一样懒惰:(
    • 关闭,但我将如何解压缩可变长度的 turple?
    • 元组,根据定义,长度不可变。
    • 很遗憾没有用于params的语法糖。假设您要编写一个函数,将一些浮点数四舍五入为最接近的整数。写Utils.Round( out x, out y, out size, out height);x = Math.Round(x); 或创建自己的数组要好得多
    【解决方案3】:

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

    在调用Fill 方法后,您将不得不处理Value 属性。

    3) 你可以使用闭包。类似于Ref&lt;T&gt; 类的实现如下所示:

    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&lt;object&gt; 更改为 Ref&lt;T&gt; 来使整个事情通用,但这意味着您的所有局部变量也应该是一个 T

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

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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      • 我讨厌人们要一把薄金属尺。我正在通过用户输入解析文本。我不知道我可以提取多少数据以及用户想要忽略哪些数据,因此我允许用户传入可变长度参数(以及一种说忽略此条目的类型),这些参数应该设置为类或数据中的值通过。因此,我想完全按照我的要求去做。我可以返回一个数组,但实际上我有已经存在的变量,并且按照我的要求更容易做到。
      • 很公平。既然已经确定不支持该语法,那么您可以编写一个为您管理它的类型吗?我的意思是,如果你可以有任何不涉及 C# 团队的语法,那会是什么?
      【解决方案5】:

      我想我可能会回答你的问题;考虑以下代码 sn-p,主“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】:

        你可以通过 ref 传递一个数组。

        编辑:

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

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

        【讨论】:

        • 把所有的东西放在一个篮子里,然后把这个新的寄出去!!
        • 这里的问题是数组会改变,但 i1、i2 等不会。将值复制回变量默认我想要的惰性方法的目的。
        • 你试过了吗?所有数组类型都是从 System.Array 隐式派生的,而 System.Array 本身是从 System.Object 派生的。这意味着所有数组始终是在托管堆上分配的引用类型,并且您的应用程序的变量包含对数组的引用,而不是数组本身。
        • @Gnoome:如果Fillarray[0] 设置为42,那么i不会接收该值。
        【解决方案7】:

        我不认为最初的提问者在 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);
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-10-01
          • 1970-01-01
          • 2014-05-04
          • 2012-09-15
          • 1970-01-01
          • 2015-04-21
          • 2023-04-09
          相关资源
          最近更新 更多