【问题标题】:Is there a way to distingish myFunc(1, 2, 3) from myFunc(new int[] { 1, 2, 3 })?有没有办法区分 myFunc(1, 2, 3) 和 myFunc(new int[] { 1, 2, 3 })?
【发布时间】:2012-03-13 13:47:09
【问题描述】:

所有 C# 向导的问题。我有一个方法,称为 myFunc,它采用可变长度/类型的参数列表。 myFunc 本身的参数签名是myFunc(params object[] args),我在列表上使用反射(例如,这有点像 printf)。

我想区别对待myFunc(1, 2, 3)myFunc(new int[] { 1, 2, 3 })。也就是说,在 myFunc 的主体中,我想枚举我的参数的类型,并希望以 { int, int, int} 而不是 int[] 结束。现在我得到后者:实际上,我无法区分这两种情况,它们都以 int[] 的形式出现。

我希望前者显示为 obs[].Length=3,obs[0]=1 等。

我曾预计后者会显示为 obs[].Length=1,其中 obs[0]={ int[3] }

这可以做到吗,还是我在问不可能?

【问题讨论】:

  • 你到底为什么需要这样的东西
  • 你怎么能指望obs[0] 是一个数组呢? obsint[],它的类型在编译时是已知的,它的元素只能是int 类型。如果您有params object[],那么您可能会争辩说object[] 也是object,但这没有意义。
  • 看看 isis2.codeplex.com。我需要去教一门课,90 分钟后回来,然后再看这个帖子。这个问题实际上与对“查询”的“回复”调用有关。文档有很多例子。 Jongboom,对不起,你发现这令人困惑。但如果你看到了目标,也许这会有所帮助。
  • @KenBirman:应该没问题,如果你有 params object[] 作为参数类型。查看我简短但完整的程序...

标签: c# params


【解决方案1】:

这样就可以了:

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("First call");
        Foo(1, 2, 3);
        Console.WriteLine("Second call");
        Foo(new int[] { 1, 2, 3 });
    }

    static void Foo(params object[] values)
    {
        foreach (object x in values)
        {
            Console.WriteLine(x.GetType().Name);
        }
    }
}

或者,如果您使用DynamicObject,您可以使用动态类型来实现类似的结果:

using System;
using System.Dynamic;

class Program
{
    static void Main(string[] args)
    {
        dynamic d = new ArgumentDumper();
        Console.WriteLine("First call");
        d.Foo(1, 2, 3);
        Console.WriteLine("Second call");
        d.Bar(new int[] { 1, 2, 3 });
    }
}

class ArgumentDumper : DynamicObject
{
    public override bool TryInvokeMember
        (InvokeMemberBinder binder,
         Object[] args,
         out Object result)
    {
        result = null;
        foreach (object x in args)
        {
            Console.WriteLine(x.GetType().Name);
        }
        return true;
    }
}

两个程序的输出:

First call
Int32
Int32
Int32
Second call
Int32[]

现在鉴于上面的输出,尚不清楚您的问题真正来自何处...尽管如果您给出的是 Foo("1", "2", "3")Foo(new string[] { "1", "2", "3" }) ,那将是另一回事 - 因为 string[]object[],但 int[] 不是。如果这是给您带来问题的真实情况,请查看动态版本 - 这两种情况都适用。

【讨论】:

  • 不知道那个。我查一下
  • 这也是我最初发布的内容,但我认为这不是 OP 想要的。
  • @Ani:嗯,输出似乎符合要求。目前尚不清楚 OP 做了哪些不同的操作,哪些不起作用,但这 确实 有效...
  • 基于 cmets 对 BasBrekelmans 的回答,我认为第二种解决方案是 OP 想要的。这需要 .Net 4.0,这并不奇怪:.Net 4.0 的目标之一是为互操作场景提供更好的支持。
  • 有趣!如果这有效,我知道我哪里出错了,但不知道为什么。事实上,我有多个版本的回复方法,它们调用将“对象”向量传递给单个主回复处理程序。我看到的问题发生在第二个过程中,而不是第一个过程中(例如,回复调用 doReply(obs) 其中 obs 是参数向量)。不知何故,编译器必须在第二次调用中失去区别,因为如果您的示例确实有效,它在回复本身中具有区别权。所以我可以很容易地修复我的代码。不知道为什么嵌套调用会出现问题,但到底是什么......
【解决方案2】:

好的,假设我们放弃了另一个问题,即您错误地认为这是编译器错误并实际解决了您的真正问题。

首先,让我们尝试陈述真正的问题。这是我的镜头:


序言:

“可变参数”方法是一种采用未指定的提前数量参数的方法。

在 C# 中实现可变参数方法的标准方法是:

void M(T1 t1, T2 t2, params P[] p)

即零个或多个必需参数后跟一个标记为“params”的数组。

当调用这样的方法时,该方法要么适用于它的普通形式(没有参数),要么适用于它的扩展形式(有参数)。也就是说,调用

void M(params object[] x){}

形式

M(1, 2, 3)

生成为

M(new object[] { 1, 2, 3 });

因为它仅适用于扩展形式。但是一个电话

M(new object[] { 4, 5, 6 });

生成为

M(new object[] { 4, 5, 6 });

而不是

M(new object[] { new object[] { 4, 5, 6 } });

因为它适用于其正常形式。

C# 支持引用类型元素数组的不安全数组协变。也就是说,string[] 可能会隐式转换为 object[],即使尝试将此类数组的第一个元素更改为非字符串会产生运行时错误。

问题:

我想拨打以下表格:

M(new string[] { "hello" });

并让这种行为就像该方法仅适用于扩展形式:

M(new object[] { new string[] { "hello" }});

而不是正常形式:

M((object[])(new string[] { "hello" }));

在 C# 中是否有一种方法可以实现不会成为不安全数组协方差和优先以其正常形式适用的方法组合的牺牲品的可变参数方法?


答案

是的,有一种方法,但你不会喜欢它。 如果您打算将单个数组传递给它,最好使该方法成为非可变的。

Microsoft 的 C# 实现支持一个未记录的扩展,它允许不使用 params 数组的 C 风格的可变参数方法。 此机制不适用于一般用途,仅适用于 CLR 团队和其他创作互操作库的人,以便他们可以编写互操作代码,在 C# 和需要 C 风格可变参数方法的语言之间架起一座桥梁。 I强烈建议反对自己尝试这样做。

这样做的机制涉及使用未记录的__arglist 关键字。一个基本的草图是:

public static void M(__arglist) 
{
    var argumentIterator = new ArgIterator(__arglist);
    object argument = TypedReference.ToObject(argumentIterator.GetNextArg());

您可以使用参数迭代器的方法遍历参数结构并获取所有参数。并且您可以使用超神奇的类型化引用对象来获取参数的类型。 甚至可以使用这种技术将变量的引用作为参数传递,但我还是不建议这样做。

这种技术特别糟糕的是调用者必须说:

M(__arglist(new string[] { "hello" }));

坦率地说,这在 C# 中看起来很恶心。现在您明白了为什么最好完全放弃可变参数方法;只需让调用者传递一个数组并完成它。

同样,我的建议是 (1) 在任何情况下,您都不应尝试使用这些未记录的 C# 语言扩展,这些扩展旨在为 CLR 实现团队和互操作库作者提供便利,并且 (2) 您应该简单地放弃可变参数方法;它们似乎不适合您的问题空间。不要与工具抗争;选择不同的工具。

【讨论】:

    【解决方案3】:

    是的,您可以,检查参数长度并检查参数类型,请参阅以下工作代码示例:

    class Program
    {
        static void Main(string[] args)
        {
            myFunc(1, 2, 3);
            myFunc(new int[] { 1, 2, 3 });
        }
    
        static void myFunc(params object[] args)
        {
            if (args.Length == 1 && (args[0] is int[]))
            {
                // called using myFunc(new int[] { 1, 2, 3 });
            }
            else
            {
                //called using myFunc(1, 2, 3), or other
            }
        }
    }
    

    【讨论】:

    • 但是,上面的代码将无法区分myFunc(new object[] { 1, 2, 3 })myFunc(1, 2, 3)
    【解决方案4】:

    您可以通过将第一个元素从列表中取出并提供额外的重载来实现类似的效果,例如:

    class Foo
    {
       public int Sum()
       {
          // the trivial case
          return 0;
       }
       public int Sum(int i)
       {
          // the singleton case
          return i;
       }
       public int Sum(int i, params int[] others)
       {
          // e.g. Sum(1, 2, 3, 4)
          return i + Sum(others);
       }
       public int Sum(int[] items)
       {
          // e.g. Sum(new int[] { 1, 2, 3, 4 });
          int i = 0;
          foreach(int q in items)
              i += q;
          return i;
       }
    }
    

    【讨论】:

    • 这实际上是我见过的这个问题的最佳解决方案——很好!
    【解决方案5】:

    这在 C# 中是不可能的。 C# 将在编译时将您的第一个调用替换为您的第二个调用。

    一种可能性是创建一个不带params 的重载并将其设为ref 参数。这可能没有意义。如果你想根据输入改变行为,也许给第二个 myFunc 另一个名字。

    更新

    我现在了解您的问题。你想要的是不可能的。如果唯一的论点是可以解析为object[] 的任何东西,则无法与此区分开来。

    您需要一个替代解决方案,也许有一个由调用者创建的字典或数组来构建参数。

    【讨论】:

    • 你没有得到图片。这里的想法是我的用户正在回复多播,他们提供的参数将由我编组(序列化)。所以我需要知道在调用我的回复函数时使用的类型,逐个参数,以便为另一端将提取数据的人正确重建签名。参数的数量和类型都很重要,我只需要一个可以接受任意参数的函数...
    • 好吧,如果其他代码有效(我打算现在检查它),显然我可以在 C# 中完成它...如果它实际上不起作用,我将取消选中答案按钮广告。否则,我就离开这里,成为一个快乐的露营者!谢谢大家...
    【解决方案6】:

    原来存在一个真正的问题,它归结为 C# 进行类型推断的方式。见this other thread的讨论

    【讨论】:

    • 这不是类型推断的问题;这是一个重载决议的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-10
    • 1970-01-01
    • 1970-01-01
    • 2010-12-02
    相关资源
    最近更新 更多