一:背景

1. 讲故事

前几天在写一个api接口,需要对衣物表进行分页查询,查询的output需要返回两个信息,一个是 totalCount,一个是 clothesList,在以前我可能需要封装一个 PagedClothes 类,如下代码:


    public class PagedClothes
    {
        public int TotalCount { getset; }
        public List<Clothes> ClothesList { getset; }
    }

    static PagedClothes GetPageList()
    {
        return new PagedClothes()
        {
            TotalCount = 100,
            ClothesList = new List<Clothes>() { }
        };
    }

在 C# 7.0 之后如果觉得封装一个类太麻烦或者没这个必要,可以用快餐写法,如下代码:


    static (int, List<Clothes>) GetPageList()
    {
        return (10new List<Clothes>() { });
    }

这里的 (int, List<Clothes>)  是什么意思呢?懂的朋友看到 (x,y) 马上就会想到它是 .NET 引入的一个新类:ValueTuple,接下来的问题就是真的是ValueTuple吗?用ILSpy 看看 IL 代码:


 .method private hidebysig static 
  valuetype [System.Runtime]System.ValueTuple`2<int32, class [System.Collections]System.Collections.Generic.List`1<class ConsoleApp2.Clothes>> GetPageList () cil managed 
 {
  IL_0000: nop
  IL_0001: ldc.i4.s 10
  IL_0003: newobj instance void class [System.Collections]System.Collections.Generic.List`1<class ConsoleApp2.Clothes>::.ctor()
  IL_0008newobj instance void valuetype [System.Runtime]System.ValueTuple`2<int32class [System.Collections]System.Collections.Generic.List`1<class ConsoleApp2.Clothes>>::.ctor(!0, !1)
  IL_000dstloc.0
  IL_000ebr.s IL_0010

  IL_0010ldloc.0
  IL_0011ret
 } // end of method Program::GetPageList

从 GetPageList 方法的 IL 代码可以很容易的看出方法返回值和return处都有 System.ValueTuple 标记,从此以后你可能就会以为 (x,y) 就是 ValueTuple 的化身 ,是吧,问题就出现在这里,这个经验靠谱吗?

二:(x,y) 真的是 ValueTuple 的化身吗

为了去验证这个经验是否靠谱,我需要举几个例子:

1. (x,y) 接收方法返回值时是 ValueTuple 吗

为了更加清楚的表述,先上代码:

        (int totalCount, List<Clothes> orders) = GetPageList();

        static (int, List<Clothes>) GetPageList()
        {
            return (10new List<Clothes>() { });
        }

现在已经知道当 (x,y) 作为方法返回值的时候在IL层面会被化作 ValueTuple,那 (x,y) 作为接受返回值的时候又是什么意思呢?还会和 ValueTuple 有关系吗?为了去验证,可以用反编译能力弱点的 dnspy 去看看反编译后的代码:

从图中可以看出,当用 (x,y) 模式接收的时候,貌似就是实现映射赋值 或者 叫做拆解赋值,是不是有一点模糊,这个 (x,y) 貌似和 ValueTuple 有关系,又貌似和 ValueTuple 没关系,不过没关系,继续看下一个例子。

2. (x,y) 在 foreach 迭代中是 ValueTuple 吗

(x,y) 也可以出现在 foreach 中,相信第一次看到这么玩还是有一点吃惊的,如下代码:


        var dict = new Dictionary<intstring>();

        foreach ((int k, string v) in dict)
        {
        }

接下来继续用 dnspy 反编译一下:

我去,这回就清晰了,从图中可以看出,我写的 (x,y) 压根就没有 ValueTuple 的影子,说明在这个场景下两者并没有任何关系,也就是说同样是 (x,y) ,放在不同位置具有不同的表现形式,这就很让人琢磨不透了, 可能有些朋友不死心,想看一下 Deconstruct 到底干了什么,知道的朋友应该明白这个新玩法叫做解构方法,继续看代码:


    public readonly struct KeyValuePair<TKey, TValue>
    {
        private readonly TKey key;
        private readonly TValue value;

        public void Deconstruct(out TKey key, out TValue value)
        {
            key = Key;
            value = Value;
        }
    }

有些抬杠的朋友会发现一个规律,貌似 (x,y) 放在赋值语句的左边都和 ValueTuple 没有任何关系,放在右边可能会有奇迹发生,那到底是不是这样呢?继续硬着头皮举例子呗。

3. (x,y) 放在赋值语句的右边是 ValueTuple 吗


    public class Point
    {
        public int X { getset; }
        public int Y { getset; }

        public Point(int x, int y)
        {
            (X, Y) = (x, y);
        }
    }

嘿嘿,看这句: (X, Y) = (x, y) 里的 (x,y) 是 ValueTuple 的化身吗?我猜你肯定是懵逼状态,是吧,亦真亦假,虚虚实实,证据还是继续反编译看呗。

我去,又是和 ValueTuple 一点关系都没有,啥玩意嘛,乱七八糟的,莫名其妙。

三:总结

说 (x,y) 是元组吧,放在 return 中就变成了 ValueTuple ,说 (x,y) 不是元组吧,放在其他处还真就不是的,是不是很疑惑,为了更加形象,在 Point 中再增加一个 Test 方法,对照一下源码和反编译的代码:


    //原始的:
    public class Point
    {
        public int X { getset; }
        public int Y { getset; }        
       public Point(int x, int y)
        {
            (X, Y) = (x, y);
        }

        public (int x, int y) Test()
        {
            return (X, Y);
        }
    }

    //反编译的:
    public class Point
    {
        public int X { getset; }
        public int Y { getset; }

        public Point(int x, int y)
        {
            this.X = x;
            this.Y = y;
        }

        [return: TupleElementNames(new string[]
        {
            "x",
            "y"
        })]
        public ValueTuple<intintTest()
        {
            return new ValueTuple<intint>(this.X, this.Y);
        }
    }

反正我已经恼火了,就这样吧,少用经验推理,多用工具挖一挖,这样才靠谱!


©著作权归作者所有:来自51CTO博客作者mb5fd86a704dffe的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 遍历 Dictionary,你会几种方式?
  2. 用过 mongodb 吧, 这三个大坑踩过吗?
  3. Linq 下的扩展方法太少了,您期待的 MoreLinq 来啦
  4. 终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的
  5. 快醒醒,C# 9 中又来了一堆关键词 init,record,with
  6. 工作十余年,还是一直被问 委托和事件 有什么区别? 真是够了
  7. C# 中的 is 真的是越来越强大,越来越语义化
  8. 我的第23个代码
  9. await,async 我要把它翻个底朝天,这回你总该明白了吧

随机推荐

  1. Android5.0挂载子系统
  2. Android之Selector、Shape介绍
  3. android的UriMatcher类
  4. android中获取网络图片
  5. Layout布局
  6. android ImageButton 左中右分段排列
  7. Android(安卓)xUtils框架(一) DbUtils
  8. Android 配置环境
  9. 根据百度地图API得到坐标和地址并在地图
  10. android 4.2 修改默锁屏为无