C# Params中的键值对

46
我正在寻找一种方式来创建如下函数:

代码:

myFunction({"Key", value}, {"Key2", value});

我确定使用匿名类型会很容易,但我还没有看到它。

我唯一想到的解决方案是使用 params KeyValuePair<String, object>[] pairs 参数,但这最终变得类似于:

myFunction(new KeyValuePair<String, object>("Key", value),
           new KeyValuePair<String, object>("Key2", value));

诚然,这样会显得丑陋。

编辑:

澄清一下,我正在编写一个名为“Message”的类,在两个不同的系统之间传递该类。它包含一个指定消息类型的ushort,以及与消息相关联的字符串到对象的字典。我想在构造函数中传递所有这些信息,以便我可以这样做:

Agent.SendMessage(new Message(MessageTypes.SomethingHappened, "A", x, "B", y, "C", z));

或类似的语法。


我编辑了我的答案,包括使用字典语法的合理方法。一定要检查一下。 - Bryan Watts
12个回答

71

当语法不好时,可以修改语法以使一个本来不错的模式能够匹配。比如:

public void MyFunction(params KeyValuePair<string, object>[] pairs)
{
    // ...
}

public static class Pairing
{
    public static KeyValuePair<string, object> Of(string key, object value)
    {
        return new KeyValuePair<string, object>(key, value);
    }
}

使用方法:

MyFunction(Pairing.Of("Key1", 5), Pairing.Of("Key2", someObject));

更有趣的是添加一个扩展方法到 string 使其可配对:

public static KeyValuePair<string, object> PairedWith(this string key, object value)
{
    return new KeyValuePair<string, object>(key, value);
}

用法:

MyFunction("Key1".PairedWith(5), "Key2".PairedWith(someObject));

编辑: 你还可以通过继承 Dictionary<,> 来使用字典语法,而不需要使用泛型尖括号:

public void MyFunction(MessageArgs args)
{
    // ...
}

public class MessageArgs : Dictionary<string, object>
{}

使用方法:

MyFunction(new MessageArgs { { "Key1", 5 }, { "Key2", someObject } });

嗯,这是一个聪明的解决方案,我喜欢它。我想这就是我要使用的。谢谢。我会暂时不回答这个问题一两个小时,看看是否还有其他的解决方案出现。 - Quantumplation
或者你可以选择按照拳击方式进行,使用 MyFunction(params object[] args) - Cole Tobin
我来自C++11,因此我喜欢你的最后一个建议。这对我来说看起来更本地化,我也觉得更易读。 - Peter VARGA

35
自从C# 7.0版本以后,你可以使用值元组。C# 7.0不仅引入了新的类型,还为元组类型和元组值提供了简化的语法。元组类型只需用大括号括起来的类型列表表示:
(string, int, double)

对应的元素被命名为Item1Item2Item3。您还可以指定可选的别名。这些别名只是语法糖(C#编译器的一个技巧);元组仍然基于不变的(但是泛型的)System.ValueTuple<T1, T2, ...>结构。
(string name, int count, double magnitude)

元组值具有类似的语法,只是您需要指定表达式而不是类型。
("test", 7, x + 5.91)

或者使用别名
(name: "test", count: 7, magnitude: x + 5.91)

带有params数组的示例:

public static void MyFunction(params (string Key, object Value)[] pairs)
{
    foreach (var pair in pairs) {
        Console.WriteLine($"{pair.Key} = {pair.Value}");
    }
}

也可以像这样解构一个元组
var (key, value) = pair;
Console.WriteLine($"{key} = {value}");

你可以在foreach循环中解构循环变量。
foreach (var (key, value) in pairs) {
    Console.WriteLine($"{key} = {value}");
}

这将把元组中的项分别提取到两个单独的变量key和value中。
现在,你可以轻松地使用不同数量的参数调用MyFunction:
MyFunction(("a", 1), ("b", 2), ("c", 3));
它使我们能够做一些像这样的事情:
DrawLine((0, 0), (10, 0), (10, 10), (0, 10), (0, 0));

查看:{{link1:C# 7.0的新功能}}

4
对我来说,这是最优雅的解决方案。 - Mark A Johnson
同意!这很优雅,但我有一个问题——有没有办法在不使用“params”的情况下使用它?谢谢! - user1044169
是的,元组可以在任何值类型(如intDateTime)可以使用的地方使用。例如,作为方法参数和返回类型、字段、属性、局部变量、泛型类型(我有没有漏掉什么?哦,对了,在其他元组中,即元组可以嵌套)。 - Olivier Jacot-Descombes

10
有趣的是,我刚刚(几分钟前)创建了一个方法,可以使用匿名类型和反射来实现这一点:
MyMethod(new { Key1 = "value1", Key2 = "value2" });


public void MyMethod(object keyValuePairs)
{
    var dic = DictionaryFromAnonymousObject(keyValuePairs);
    // Do something with the dictionary
}

public static IDictionary<string, string> DictionaryFromAnonymousObject(object o)
{
    IDictionary<string, string> dic = new Dictionary<string, string>();
    var properties = o.GetType().GetProperties();
    foreach (PropertyInfo prop in properties)
    {
        dic.Add(prop.Name, prop.GetValue(o, null) as string);
    }
    return dic;
}

太棒了!这正是我在寻找的解决方案。由于反射,会有多少额外开销? - Quantumplation
不知道... 无论如何,这个解决方案很方便,但如果您的应用程序对性能要求很高,则不太适合。 - Thomas Levesque

7
有点儿hack,但你可以让你的Message类实现IEnumerable接口并提供一个Add方法。然后你就可以使用集合初始化语法了:
Agent.SendMessage
(
    new Message(MessageTypes.SomethingHappened) {{ "foo", 42 }, { "bar", 123 }}
);

// ...

public class Message : IEnumerable
{
    private Dictionary<string, object> _map = new Dictionary<string, object>();

    public Message(MessageTypes mt)
    {
        // ...
    }

    public void Add(string key, object value)
    {
        _map.Add(key, value);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)_map).GetEnumerator();
        // or throw a NotImplementedException if you prefer
    }
}

这似乎比反射方法的开销要小得多,而且实际上可能根本不是黑客攻击,因为无论如何我都想要一个添加函数。 - Quantumplation
1
根据您希望Message类像真正的集合一样运行的程度,您可以实现比IEnumerable更大的接口(例如IDictionary<K,V>)。我之所以选择IEnumerable,是因为它允许使用最少的额外工作使用集合初始化器语法! - LukeH

3

使用字典:

myFunction(new Dictionary<string, object>(){
  {"Key", value}, 
  {"Key2", value}});

这很简单,你只需要一个new Dictionary<K, V>,而不是每个参数都需要。获取键和值是微不足道的。

或者使用匿名类型:

myFunction(new {
  Key = value, 
  Key2 = value});

在函数内使用这种方法并不是很好,你需要使用反射。代码大致如下:

foreach (PropertyInfo property in arg.GetType().GetProperties())
{
  key = property.Name;
  value = property.GetValue(arg, null);
}

(直接从我的脑海中想到的,可能有些错误...)


我该如何从第二个类型中提取键/值对? - Quantumplation
在asp.net MVC的ActionLink方法中找到第二个。这是否需要使用反射来获取属性名称和值? - Chris S

2
您也可以引用NuGet包“valuetuple”,它允许您执行以下操作:
public static void MyFunction(params ValueTuple<string, object>[] pairs)
{
    var pair = pairs[1];
    var stringValue = pair.item1;
    var objectValue = pair.item2;
}

您可以像这样调用该方法:

MyFunction(("string",object),("string", object));

2

使用C# 4.0中的动态类型:

public class MyClass
{
    // Could use another generic type if preferred
    private readonly Dictionary<string, dynamic> _dictionary = new Dictionary<string, dynamic>();

    public void MyFunction(params dynamic[] kvps)
    {
        foreach (dynamic kvp in kvps)
            _dictionary.Add(kvp.Key, kvp.Value);
    }
}

使用以下方法进行调用:

MyFunction(new {Key = "Key1", Value = "Value1"}, new {Key = "Key2", Value = "Value2"});

2

使用字典...

void Main()
{
    var dic = new Dictionary<string, object>();
    dic.Add( "Key1", 1 );
    dic.Add( "Key2", 2 );   

    MyFunction( dic ).Dump();
}

public static object MyFunction( IDictionary dic )
{
   return dic["Key1"];
}

1
是的,但由于需要先声明一个字典,所以在这里调用函数比较复杂。此外,.Dump() 是什么作用? - Quantumplation
哎呀,Dump 是来自 LINQPad 的,就像 Console.Writeline 一样。你为什么要把所有东西都放在一个语句中强行扭曲自己呢?你应该让你的接口正确无误。关注调用约定应该是次要的。 - JP Alioto
2
这是一个 Message 类的构造函数,它经常被调用,而且笨拙的调用约定会在后面造成麻烦。对于许多不同实例发送消息的情况,需要声明一个字典、添加键并将其传递给构造函数变得难以忍受,并且会分散读者/编写者的注意力,从而变得更难维护。 - Quantumplation

2
以下是更多相关内容:

这里有更多类似的内容:

static void Main(string[] args)
{
    // http://msdn.microsoft.com/en-us/library/bb531208.aspx
    MyMethod(new Dictionary<string,string>()
    {
        {"key1","value1"},
        {"key2","value2"}
    });
}

static void MyMethod(Dictionary<string, string> dictionary)
{
    foreach (string key in dictionary.Keys)
    {
        Console.WriteLine("{0} - {1}", key, dictionary[key]);
    }
}

在这里可以找到一些关于初始化字典的详细信息


1
你可以这样做:
TestNamedMethod(DateField => DateTime.Now, IdField => 3);

其中DateFieldIdField应该是“字符串”标识符。

TestNameMethod

public static string TestNameMethod(params Func<object, object>[] args)
{
    var name = (args[0].Method.GetParameters()[0]).Name;
    var val = args[0].Invoke(null);
    var name2 = (args[1].Method.GetParameters()[0]).Name;
    var val2 = args[1].Invoke(null);
    Console.WriteLine("{0} : {1}, {2} : {3}", name, val, name2, val2);
}

性能比使用字典快5%。缺点:您无法将变量用作键。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接