哪里有园林狗尾草种子? 当前位置:首页>哪里有园林狗尾草种子?>正文

哪里有园林狗尾草种子?

发布时间:2019-04-26

原标题:解析 C# 7中的元组类型(ValueTuple)

System.Tuple 类型是在.NET 4.0中引入的,但是有两个明显的缺点:

(1) Tuple 类型是引用类型。

(2) 没有构造函数支持。

为了解决这些问题,C# 7 引入了新的语言功能以及新的类型(*)。

现在,如果您需要从函数中返回两个值的合并结果,或者把两个值合并到一个哈希表中,可以使用System.ValueTuple类型并使用一个精短的语法来构造它们:

    // 构建元组实例
    var tpl = (1, 2);
                
    // 在字典中使用元组
    var d = new Dictionary<(int x, int y), (byte a, short b)>();
     
    // 不同名称的元组是兼容的
    d.Add(tpl, (a: 3, b: 4));
     
    // 元组值的语义
    if (d.TryGetValue((1, 2), out var r))
    {
        // 解构元组忽略第一个元素
        var (_, b) = r;
                    
        // 使用命名语法和定义名称
        Console.WriteLine($"a: {r.a}, b: {r.Item2}");
    }

(*) System.ValueTuple 类型在.NET Framework 4.7中引入。但是您仍然可以在较低的框架版本中使用这个功能,这时候,您必须引用一个特殊的nuget包:System.ValueTuple。

  • 元组声明的语法与函数参数声明相似:(Type1 name1, Type2 name2)
  • 元组的构造语法类似于参数构造:(value1, optionalName: value2)
  • 两个元组具有相同的元素类型,但不同的名称是兼容(**):(int a, int b) = (1, 2)
  • 元组值的语义: (1,2).Equals((a: 1, b: 2))(1,2).GetHashCode() == (1,2).GetHashCode() 返回的值均是true
  • 元组不支持==!=。在github上有一个悬而未决的讨论:“支持==和!=元组类型”。
  • 元组可以被“解构”,但只能转换成“变量声明”,而不能“out var”或case语句中转换:var (x, y) = (1,2) - OK, (var x, int y) = (1,2) - OK, dictionary.TryGetValue(key, out var (x, y)) - not OK, case var (x, y): break; - not OK。
  • 元组是可变的:(int a, int b) x = (1,2); x.a++;.
  • 元组元素可以通过名称(如果提供的话)或通过通用名称Item1Item2等来访问。

(**) 我们马上就会明白上面几点。

元组名称

缺少用户定义的名称导致System.Tuple类型不常用。我们可以将System.Tuple用作一个精减方法的实现细节,但如果我们需要传递它,我更喜欢使用具有描述性属性名称的命名类型。新元组功能很好地解决了这个问题:可以为元组元素指定名称,而不像匿名类型,即使在不同的程序集中也可以使用这些名称。

C#编译器为方法签名中使用的每个元组类型指定了一个特殊的标记TupleElementNamesAttribute(***) :

(***)TupleElementNamesAttribute标记非常特殊,不能在用户代码中直接使用。如果您尝试使用它,编译器会报出错误。

    public (int a, int b) Foo1((int c, int d) a) => a;
 
    [return: TupleElementNames(new[] { "a", "b" })]
    public ValueTuple<int, int> Foo(
        [TupleElementNames(new[] { "c", "d" })] ValueTuple<int, int> a)
    {
        return a;
    }

这有助于IDE和编译器“检查”元素名称,并警告错误地使用它们:

    // 正确: 元组声明可以跳过元素名称
    (int x, int y) tpl = (1, 2);
     
    // 警告: 由于目标类型“(int x, int y)”指定了其他名称或未指定名称,因此元组元素名称“a”被忽略。
    tpl = (a:1, b:2);
     
    // 正确 :元组解构忽略元素名称
    var (a, b) = tpl;
     
    // x: 2, y: 1. 元组名被忽略
    var (y, x) = tpl;

编译器对继承的成员有较强的要求:

    public abstract class Base
    {
        public abstract (int a, int b) Foo();
        public abstract (int, int) Bar();
    }
     
    public class Derived : Base
    {
        // 错误:替代继承成员“Base.Foo()”时无法更改元组元素名称
        public override (int c, int d) Foo() => (1, 2);
        // 错误:替代继承成员“Base.Bar()”时无法更改元组元素名称
        public override (int a, int b) Bar() => (1, 2);
    }

常规方法参数可以在重写成员中自由更改,重写成员中的元组元素名称应该与基本类型中的元素名称完全匹配。

元素名称推断

C# 7.1 引入了一个额外的增强功能:元素名称推断类似于C#为匿名类型所做的推断。

    public void NameInference(int x, int y)
    {
        // (int x, int y)
        var tpl = (x, y);
     
        var a = new {X = x, Y = y};
     
        // (int X, int Y)
        var tpl2 = (a.X, a.Y);
    }

值语义和可变性

元组是公共字段可变的值类型。这听起来令人担忧,因为我们知道可变值类型被认为是有害的。这是一个邪恶的小例子:

    var x = new { Items = new List<int> { 1, 2, 3 }.GetEnumerator() };
    while (x.Items.MoveNext())
    {
        Console.WriteLine(x.Items.Current);
    }

如果运行这个代码,您会得到一个无限循环。List&lt;T&gt;.Enumerator是一个可变值类型,但是Items是属性。这意味着x.Items在每个循环迭代中返回原始迭代器的副本,从而导致无限循环。

但是只有当数据与行为混合在一起时,可变值类型才是危险的:枚举器拥有一个状态(当前元素)并具有行为(通过调用MoveNext方法来推进迭代器的能力)。这种组合可能会导致问题,因为在副本上调用方法而不是在原始实例上调用方法,从而导致无效操作。下面是一组由于值类型的隐藏副本而导致不明显行为的示例:gist。

但可变性问题依然存在:

    var tpl = (x: 1, y: 2);
    var hs = new HashSet<(int x, int y)>();
    hs.Add(tpl);
     
    tpl.x++;
    Console.WriteLine(hs.Contains(tpl)); // false

元组在字典中作为键是非常有用的,并且由于适当的值语义可以存储在哈希表中。但是您不应该在集合的不同操作之间改变一个元组变量的状态。

解构

虽然元组的构造函数对于元组来说非常特殊的,但是解构非常通用,并且可以与任何类型一起使用。

    public static class VersionDeconstrucion
    {
        public static void Deconstruct(this Version v, out int major, out int minor, out int build, out int revision)
        {
            major = v.Major;
            minor = v.Minor;
            build = v.Build;
            revision = v.Revision;
        }
    }
     
    
    var version = Version.Parse("1.2.3.4");
    var (major, minor, build, _) = version;
     
    // Prints: 1.2.3
    Console.WriteLine($"{major}.{minor}.{build}");

解构使用“鸭子类型(duck-typing)”的方法:如果编译器可以找到一个方法调用Deconstruct给定的类型 - 实例方法或扩展方法 - 类型即是可解构的。

元组别名

一旦您开始使用元组,很快就会意识到想在源代码的多个地方“重用”一个元组类型,但这并没有什么问题。首先,虽然C#不支持给定类型的全局别名,不过您可以使用“using”别名指令,它会在一个文件中创建一个别名;其次,您不能将元组指定别名:

//您不能这样做:编译错误
using Point = (int x, int y);
 
// 但是您可以这样做
using SetOfPoints = System.Collections.Generic.HashSet<(int x, int y)>;

github上有一个关于“使用指令中的元组类型”的讨论。所以,如果您发现自己在多个地方使用一个元组类型,你有两个选择:保持复制粘贴或创建一个命名的类型。

命名规则

下面是一个有趣的问题:我们应该遵循什么命名规则来处理元组元素?Pascal规则喜欢ElementName还是骆峰规则elementName?一方面,元组元素应该遵循公共成员的命名规则(即PascalCase),但另一方面,元组只是包含变量的变量,变量应该遵循骆峰规则。

如果元组被用作参数或方法的返回类型使用PascalCase规则,并且如果在函数中本地创建元组使用camelCase规则,可以考虑使用基于用法和使用的不同命名方案。但我更喜欢总是使用camelCase

总结

我发现元组在日常工作中非常有用。我需要不止一个函数返回值,或者我需要把一对值放入一个哈希表,或者字典的Key非常复杂,我需要用另一个“字段”来扩展它。

我甚至使用它们来避免与方法类似的ConcurrentDictionary.TryGetOrAdd的闭包分配,需要额外的参数。在许多情况下,状态也是一个元组。

该功能是非常有用的,但我还想看到一些增强功能:

  1. 全局别名:能够“命名”一个元组并在整个程序集中使用它们(****)。
  2. 在模式匹配中解构一个元组:out varcase var语法。
  3. 使用运算符==进行相等比较。

(****)我知道这些功能是有争议的,但我认为它非常有用的。我们可以等待Record类型,但还不确定Record是值类型还是引用类型。

原文:《Dissecting the tuples in C# 7》https://blogs.msdn.microsoft.com/seteplia/2017/11/01/dissecting-the-tuples-in-c-7/
翻译:Sweet Tang
本文地址:http://www.cnblogs.com/tdfblog/p/dissecting-the-tuples-in-c-7.html
欢迎转载,请在明显位置给出出处及链接。

当前文章:http://dehangcd.com/54x5u.html

发布时间:2019-04-26 00:47:22

低到一毛钱的连翘苗,现场实拍图片,沭阳苗源最足的基地 我们美人蕉种植的太多,还有300多万棵苗没卖掉 秋天适合做花海的花有哪些? 5月可以播种凤仙花吗? 黄菊花种子发芽温度最佳是几度? 指甲花种子报价哪家准确? 8月可以播种合欢树吗? 哪里有出售紫薇树种子? 红花楹种子什么时间育苗? 香花槐陈种子能种出吗?

82151 59403 46507 96378 65997 43064 87039 69414 92863 75772 45743 11295 18872 42371 66269 46296 13571 10993 49291 81593 46040 86792 72450

责任编辑:公乙邓王

随机推荐