【问题标题】:C# Tuple/List/Generics - is there a shorter way?C# Tuple/List/Generics - 有没有更短的方法?
【发布时间】:2013-02-04 23:28:06
【问题描述】:

我编写了一个解析命令行参数的程序或算法。首先,需要指定有效参数及其属性(标志、分隔符等)。

现在,我有一个帮助函数,它返回:Tuple<Tuple<Argument, String>, String>:元组中的第一个元素(也是一个元组)包含一个“键值对”,null 如果找不到对。第二个元素包含一个被标识为选项的字符串,分隔符为" "(意味着该值将是命令行中的下一个元素)

该函数的代码如下:

private Tuple<Tuple<Argument, String>, Argument> f(string e, List<Argument> valid) {
    var p = valid.Find( x => { return e.StartsWith( x.value ); } );
    if ( p == null )
        return new Tuple<Tuple<Argument, String>, Argument>( null, null );
    if ( p.flag )
        return new Tuple<Tuple<Argument, String>, Argument>( new Tuple<Argument, String>( p, "" ), null );
    if ( p.separator.Equals(" ") && p.value.Length == e.Length )
        return new Tuple<Tuple<Argument, String>, Argument>( null, p );
    var si = e.IndexOf(p.separator);
    if ( si != p.value.Length )
        return new Tuple<Tuple<Argument, String>, Argument>( null, null );
    return new Tuple<Tuple<Argument, String>, Argument>( new Tuple<Argument, String>( p, e.Substring( si + p.separator.Length ) ), null );
}

有什么办法可以写得更短吗?我必须明确将泛型放在那里吗?

显然,为此创建自己的类是一种解决方案。但不是还有一个吗?

作为参考,Scala 中的相同代码如下所示:

val po = valid.find(p => e.startsWith(p.value))
po match {
    case Some(p) =>
        if ( p.flag )
            (Some((p, "")), None)
        else if ( p.separator.matches(" |\t") && p.value.length == e.length )
            (None, Some(p))
        else {
            val si = e.indexOf(p.separator)
            if ( si != p.value.length )
                (None, None)
            else
                (Some((p, e.substring(si + p.separator.length))), None)
        }
    case _ => (None, None)
}

另外,有没有办法以紧凑的方式启动ListsTuples?如List(1, 2, 3, 4)(Key, Value) 而不是Tuple&lt;KeyType, ValueType&gt;(Key, Value)

问候,
丹尼尔。

【问题讨论】:

  • 为什么是嵌套元组?为什么不使用三元素元组? Tuple&lt;Argument,String, Argument&gt;?
  • 我几乎从不使用元组。自定义类或匿名类型可以让您的代码在 99% 的时间里更简洁、更短。
  • 没有真正的原因,除了使用三元素元组它对于代码实际在做什么是不透明的。前两个元素形成键值对的三元素元组不是很清楚,因此嵌套元组。
  • 我的问题是我无法理解您在这里晦涩难懂的意图。否则我也许可以帮助你。 我的意思是:请发布一个命令行示例以及您想用它实现什么。
  • 不能更同意@HighCore。有时使用元组对一系列思路进行原型设计很好,但是一旦您开始看到这种讨厌的东西 (Tuple&lt;Tuple&lt;Argument, String&gt;, Argument&gt;),您可能应该考虑另一种策略。

标签: c# generics boilerplate


【解决方案1】:

您可以使用 typedef 来清理代码。从技术上讲,C# 没有 typedef,但您可以使用 usings 来模拟它们。像这样的:

using MyTuple = Tuple<Tuple<Argument, string>, Argument>;
using MyTuple2 = Tuple<Argument, string>;

public class MyClass {

    private MyTuple f(string e, List<Argument> valid) {
        var p = valid.Find( x => { return e.StartsWith( x.value ); } );
        if ( p == null )
           return new MyTuple( null, null );
        if ( p.flag )
           return new MyTuple( new MyTuple2( p, "" ), null );
        if ( p.separator.Equals(" ") && p.value.Length == e.Length )
            return new MyTuple( null, p );
        var si = e.IndexOf(p.separator);
        if ( si != p.value.Length )
            return MyTuple( null, null );
        return new MyTuple( new MyTuple2( p, 
            e.Substring( si + p.separator.Length ) ), null );
    }
}

关于您关于初始化的问题,我不确定我是否理解。元组已经有一个紧凑的初始化方法,使用构造函数参数。

列表可以使用初始化器,如下所示:

var s = new List<string> { "one", "two", "three" };

【讨论】:

  • 谢谢,这正是我要找的。我有点希望 C# 会自动查看我使用的元素,所以我不必像 List&lt;&gt; { "one" }; 那样显式定义泛型,但显然这是不可能的。我是 C# 新手,所以还在学习!
【解决方案2】:

看一下您说明显式声明元组类型的“丑陋”的行:

// ugly code
return new Tuple<Tuple<Argument, String>, Argument>( new Tuple<Argument, String>( p, "" ), null );

1) 惯用的 C# 可能会显式定义一个类

class ArgumentMatch {
    public Argument Key;
    public string Value;
    public Argument Unmatched;
}
// if you define the constructor
return new ArgumentMatch(p, "", null);
// or using object initializer syntax,
return new ArgumentMatch { Key = p, Value = "" };

2) 您可以编写自己的通用实用程序函数来避免显式的元组声明。 C# 将推断函数的类型,而不是构造函数。

// using standard static method
static Tuple<T,U> MakeTuple<T,U>(T a, U b) { return new Tuple<T,U>(a,b); }
// and extension method (I don't personally like this since it's a bit 
// of a name-space pollutant)
static Tuple<T,U> Pair<T,U>(this T a, U b) { return new Tuple<T,U>(a,b); }

return MakeTuple(MakeTuple(p, ""), (Argument)null); 
return p.Pair("").Pair((Argument)null);

3) F# 几乎是 scala 示例的逐行翻译。如果您可以使用 scala,您可以考虑在 .NET 中使用 F#。

// F# example code
match valid.find(fun x -> e.StartsWith(x.Value)) with
| Some(p) ->
    if p.flag then
        (p, ""),None
    elif p.separator = " " && p.Value.Length = e.Length then
        (None,None),Some(p)
 ....

【讨论】:

  • 关于 1)您可能应该提到对象初始化器需要属性(而不是字段)。
【解决方案3】:

这个问题可能很老了。

然而,4 年后,Microsoft 在 C# 7 中实现了 ValueTuples。 因此,您可以简单地返回(T1 a, T2 b),而不是返回Tuples&lt;T1,T2&gt;。 更多细节在这里https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7#tuples

这个元组使用System.ValueTuple,它是一个结构(值类型),而不是System.Tuple,它是一个类(引用类型)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-04
    相关资源
    最近更新 更多