zoukankan      html  css  js  c++  java
  • 表达式树练习实践:C# 五类运算符的表达式树表达

    表达式树练习实践:C# 运算符

    在 C# 中,算术运算符,有以下类型

    • 算术运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 赋值运算符
    • 其他运算符

    这些运算符根据参数的多少,可以分作一元运算符、二元运算符、三元运算符。本文将围绕这些运算符,演示如何使用表达式树进行操作。

    对于一元运算符和二元运算符的 Expression 的子类型如下:

    UnaryExpression; //一元运算表达式
    BinaryExpression; //二元运算表达式
    

    一,算术运算符

    运算符 描述
    + 把两个操作数相加
    - 从第一个操作数中减去第二个操作数
    * 把两个操作数相乘
    / 分子除以分母
    % 取模运算符,整除后的余数
    ++ 自增运算符,整数值增加 1
    -- 自减运算符,整数值减少 1

    + 与 Add()

    正常代码

                int a;
                int b;
                a = 100;
                b = 200;
                var ab = a + b;
                Console.WriteLine(ab);
    

    使用表达式树构建

                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
    
                // ab = a + b
                BinaryExpression ab = Expression.Add(a, b);
    
                // 打印 a + b 的值
                MethodCallExpression method = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), ab);
    
                Expression<Action<int, int>> lambda = Expression.Lambda<Action<int, int>>(method, a, b);
                lambda.Compile()(100, 200);
    
                Console.ReadKey();
    

    如果想复杂一些,使用 来执行:

                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
    
                // 别忘记了赋值
                BinaryExpression aa = Expression.Assign(a, Expression.Constant(100, typeof(int)));
                BinaryExpression bb = Expression.Assign(b, Expression.Constant(200, typeof(int)));
    
                // ab = a + b
                BinaryExpression ab = Expression.Add(a, b);
    
                // 打印 a + b 的值
                MethodCallExpression method = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), ab);
    
                // 以块的形式执行代码,相当于{ }
                // 不需要纠结这里,后面会有详细说明,重点是上面
                var call = Expression.Block(new ParameterExpression[] { a, b }, aa, bb, method);
                Expression<Action> lambda = Expression.Lambda<Action>(call);
                lambda.Compile()();
    

    上面两个示例,是使用表达式树计算结果,然后还是使用表达式树打印结果。

    前者依赖外界传入参数值,赋予 a、b,后者则全部使用表达式树赋值和运算。

    那么,如何通过表达式树执行运算,获取执行结果呢?

                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
    
                // ab = a + b
                BinaryExpression ab = Expression.Add(a, b);
    
                Expression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(ab, a, b);
                int result = lambda.Compile()(100, 200);
    
                Console.WriteLine(result);
                Console.ReadKey();
    

    这些区别在于如何编写 Expression.Lambda()

    另外,使用 AddChecked() 可以检查操作溢出。

    - 与 Subtract()

    与加法一致,此处不再赘述,SubtractChecked() 可以检查溢出。

    a - b ,结果是 100 。

                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
    
                // ab = a - b
                BinaryExpression ab = Expression.Subtract(a, b);
    
                Expression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(ab, a, b);
                int result = lambda.Compile()(200, 100);
    
                Console.WriteLine(result);
    

    乘除、取模

    乘法

                // ab = a * b
                BinaryExpression ab = Expression.Multiply(a, b);
    // ab = 20000
    

    除法

                // ab = a / b
                BinaryExpression ab = Expression.Divide(a, b);
    // ab = 2
    

    取模(%)

                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
    
                // ab = a % b
                BinaryExpression ab = Expression.Modulo(a, b);
    
                Expression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(ab, a, b);
                int result = lambda.Compile()(200, 150);
    // ab = 50
                Console.WriteLine(result);
                Console.ReadKey();
    

    自增自减

    自增自减有两种模型,一种是 x++ 或 x--,另一种是 ++x 或 --x

    他们都是属于 UnaryExpression 类型。

    算术运算符 表达式树 说明
    x++ Expression.PostIncrementAssign() 后置
    x-- Expression.PostDecrementAssign() 后置
    ++x Expression.PreIncrementAssign() 前置
    --x Expression.PreDecrementAssign() 前置

    巧记:Post 后置, Pre 前置;Increment 是加,Decrement是减;Assign与赋值有关(后面会说到);

    x++x-- 的使用

                int a = 10;
                int b = 10;
                a++;
                b--;
                Console.WriteLine(a);
                Console.WriteLine(b);
    
                // int a,b;
                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
    
                // a = 10,b = 10;
                BinaryExpression setA = Expression.Assign(a, Expression.Constant(10));
                BinaryExpression setB = Expression.Assign(b, Expression.Constant(10));
    
                // a++
                UnaryExpression aa = Expression.PostIncrementAssign(a);
    
                // b--
                UnaryExpression bb = Expression.PostDecrementAssign(b);
    
                //Console.WriteLine(a);
                //Console.WriteLine(b);
                MethodCallExpression callA = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a);
                MethodCallExpression callB = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), b);
    
                BlockExpression block = Expression.Block(
                    new ParameterExpression[] { a, b },
                    setA,
                    setB,
                    aa,
                    bb,
                    callA,
                    callB
                    );
    
                Expression<Action> lambda = Expression.Lambda<Action>(block);
                lambda.Compile()();
    
                Console.ReadKey();
    

    如果想把参数从外面传入,设置 a,b

                // int a,b;
                ParameterExpression a = Expression.Variable(typeof(int), "a");
                ParameterExpression b = Expression.Variable(typeof(int), "b");
    
                // a++
                UnaryExpression aa = Expression.PostIncrementAssign(a);
    
                // b--
                UnaryExpression bb = Expression.PostDecrementAssign(b);
    
                //Console.WriteLine(a);
                //Console.WriteLine(b);
                MethodCallExpression callA = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a);
                MethodCallExpression callB = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), b);
    
                BlockExpression block = Expression.Block(
                    aa,
                    bb,
                    callA,
                    callB
                    );
    
                Expression<Action<int, int>> lambda = Expression.Lambda<Action<int, int>>(block, a, b);
                lambda.Compile()(10, 10);
                Console.ReadKey();
    

    生成的表达式树如下

    .Lambda #Lambda1<System.Action`2[System.Int32,System.Int32]>(
        System.Int32 $a,
        System.Int32 $b) {
        .Block() {
            $a++;
            $b--;
            .Call System.Console.WriteLine($a);
            .Call System.Console.WriteLine($b)
        }
    }
    

    为了理解一下 Expression.Block(),可以在这里学习一下(后面会说到 Block())。

                // int a,b;
                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
                ParameterExpression c = Expression.Variable(typeof(int), "c");
    
                BinaryExpression SetA = Expression.Assign(a, c);
                BinaryExpression SetB = Expression.Assign(b, c);
                // a++
                UnaryExpression aa = Expression.PostIncrementAssign(a);
    
                // b--
                UnaryExpression bb = Expression.PostDecrementAssign(b);
    
                //Console.WriteLine(a);
                //Console.WriteLine(b);
                MethodCallExpression callA = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a);
                MethodCallExpression callB = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), b);
    
                BlockExpression block = Expression.Block(
                    new ParameterExpression[] { a, b },
                    SetA,
                    SetB,
                    aa,
                    bb,
                    callA,
                    callB
                    );
    
                Expression<Action<int>> lambda = Expression.Lambda<Action<int>>(block, c);
                lambda.Compile()(10);
    
                Console.ReadKey();
    

    为什么这里要多加一个 c 呢?我们来看看生成的表达式树

    .Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $c) {
        .Block(
            System.Int32 $a,
            System.Int32 $b) {
            $a = $c;
            $b = $c;
            $a++;
            $b--;
            .Call System.Console.WriteLine($a);
            .Call System.Console.WriteLine($b)
        }
    }
    

    观察一下下面代码生成的表达式树

                // int a,b;
                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
    
                // a++
                UnaryExpression aa = Expression.PostIncrementAssign(a);
    
                // b--
                UnaryExpression bb = Expression.PostDecrementAssign(b);
    
                //Console.WriteLine(a);
                //Console.WriteLine(b);
                MethodCallExpression callA = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a);
                MethodCallExpression callB = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), b);
    
                BlockExpression block = Expression.Block(
                    new ParameterExpression[] { a, b },
                    aa,
                    bb,
                    callA,
                    callB
                    );
    
                Expression<Action<int, int>> lambda = Expression.Lambda<Action<int, int>>(block, a, b);
                lambda.Compile()(10, 10);
                Console.ReadKey();
    
    .Lambda #Lambda1<System.Action`2[System.Int32,System.Int32]>(
        System.Int32 $a,
        System.Int32 $b) {
        .Block(
            System.Int32 $a,
            System.Int32 $b) {
            $a++;
            $b--;
            .Call System.Console.WriteLine($a);
            .Call System.Console.WriteLine($b)
        }
    }
    

    关于前置的自增自减,按照上面示例编写即可,但是需要注意的是, ++x 和 --x ,是“先运算后增/自减”。

    二,关系运算符

    ==、!=、>、<、>=、<=

    C# 中的关系运算符如下

    运算符 描述
    == 检查两个操作数的值是否相等,如果相等则条件为真。
    != 检查两个操作数的值是否相等,如果不相等则条件为真。
    > 检查左操作数的值是否大于右操作数的值,如果是则条件为真。
    < 检查左操作数的值是否小于右操作数的值,如果是则条件为真。
    >= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。
    <= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。

    == 表示相等比较,如果是值类型和 string 类型,则比较值是否相同;如果是引用类型,则比较引用的地址是否相等。

    其它的关系运算符则是仅比较值类型的大小。

    实例代码

                int a = 21;
                int b = 10;
                Console.Write("a == b:");
                Console.WriteLine(a == b);
    
                Console.Write("a < b :");
                Console.WriteLine(a < b);
    
    
                Console.Write("a > b :");
                Console.WriteLine(a > b);
    
                // 改变 a 和 b 的值 
                a = 5;
                b = 20;
    
                Console.Write("a <= b:");
                Console.WriteLine(a <= b);
    
    
                Console.Write("a >= b:");
                Console.WriteLine(b >= a);
    
                Console.ReadKey();
    

    使用表达式树实现

                // int a,b;
                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
    
                // a = 21,b = 10;
                BinaryExpression setA = Expression.Assign(a, Expression.Constant(21));
                BinaryExpression setB = Expression.Assign(b, Expression.Constant(20));
    
                // Console.Write("a == b:");
                // Console.WriteLine(a == b);
                MethodCallExpression call1 = Expression.Call(null,
                    typeof(Console).GetMethod("Write", new Type[] { typeof(string) }),
                    Expression.Constant("a == b:"));
                MethodCallExpression call11 = Expression.Call(null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(bool) }),
                    Expression.Equal(a, b));
    
                // Console.Write("a < b :");
                // Console.WriteLine(a < b);
                MethodCallExpression call2 = Expression.Call(null,
                    typeof(Console).GetMethod("Write", new Type[] { typeof(string) }),
                    Expression.Constant("a < b :"));
                MethodCallExpression call22 = Expression.Call(null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(bool) }),
                    Expression.LessThan(a, b));
    
                // Console.Write("a > b :");
                // Console.WriteLine(a > b);
                MethodCallExpression call3 = Expression.Call(null,
                    typeof(Console).GetMethod("Write", new Type[] { typeof(string) }),
                    Expression.Constant("a > b :"));
                MethodCallExpression call33 = Expression.Call(null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(bool) }),
                    Expression.GreaterThan(a, b));
    
    
                // 改变 a 和 b 的值 
                // a = 5;
                // b = 20;
                BinaryExpression setAa = Expression.Assign(a, Expression.Constant(5));
                BinaryExpression setBb = Expression.Assign(b, Expression.Constant(20));
    
                // Console.Write("a <= b:");
                // Console.WriteLine(a <= b);
                MethodCallExpression call4 = Expression.Call(null,
                    typeof(Console).GetMethod("Write", new Type[] { typeof(string) }),
                    Expression.Constant("a <= b:"));
                MethodCallExpression call44 = Expression.Call(null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(bool) }),
                    Expression.LessThanOrEqual(a, b));
    
                // Console.Write("a >= b:");
                // Console.WriteLine(b >= a);
                MethodCallExpression call5 = Expression.Call(null,
                    typeof(Console).GetMethod("Write", new Type[] { typeof(string) }),
                    Expression.Constant("a >= b:"));
                MethodCallExpression call55 = Expression.Call(null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(bool) }),
                    Expression.GreaterThanOrEqual(a, b));
    
                BlockExpression block = Expression.Block(new ParameterExpression[] { a, b },
                    setA,
                    setB,
                    call1,
                    call11,
                    call2,
                    call22,
                    call3,
                    call33,
                    setAa,
                    setBb,
                    call4,
                    call44,
                    call5,
                    call55
                    );
    
                Expression<Action> lambda = Expression.Lambda<Action>(block);
                lambda.Compile()();
                Console.ReadKey();
    

    生成的表达式树如下

    .Lambda #Lambda1<System.Action>() {
        .Block(
            System.Int32 $a,
            System.Int32 $b) {
            $a = 21;
            $b = 20;
            .Call System.Console.Write("a == b:");
            .Call System.Console.WriteLine($a == $b);
            .Call System.Console.Write("a < b :");
            .Call System.Console.WriteLine($a < $b);
            .Call System.Console.Write("a > b :");
            .Call System.Console.WriteLine($a > $b);
            $a = 5;
            $b = 20;
            .Call System.Console.Write("a <= b:");
            .Call System.Console.WriteLine($a <= $b);
            .Call System.Console.Write("a >= b:");
            .Call System.Console.WriteLine($a >= $b)
        }
    }
    

    三,逻辑运算符

    &&、||、!

    运算符 描述
    && 称为逻辑与运算符。如果两个操作数都非零,则条件为真。
    || 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。
    ! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。

    逻辑运算符的运行,结果是 true 或 false。

    逻辑运算符 表达式树
    && Expression.AndAlso()
    || Expression.OrElse()
    Expression.Not()
                int a = 10;
                int b = 11;
    
                Console.Write("[a == b && a > b]:");
                Console.WriteLine(a == b && a > b);
    
                Console.Write("[a > b || a == b]:");
                Console.WriteLine(a > b || a == b);
    
                Console.Write("[!(a == b)]:");
                Console.WriteLine(!(a == b));
                Console.ReadKey();
    

    使用表达式树编写

                //int a = 10;
                //int b = 11;
                ParameterExpression a = Expression.Parameter(typeof(int), "a");
                ParameterExpression b = Expression.Parameter(typeof(int), "b");
                BinaryExpression setA = Expression.Assign(a, Expression.Constant(10));
                BinaryExpression setB = Expression.Assign(b, Expression.Constant(11));
    
                //Console.Write("[a == b && a > b]:");
                //Console.WriteLine(a == b && a > b);
                MethodCallExpression call1 = Expression.Call(null, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }), Expression.Constant("[a == b && a > b]:"));
    
                MethodCallExpression call2 = Expression.Call(
                    null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(bool) }),
                     Expression.AndAlso(Expression.Equal(a, b), Expression.GreaterThan(a, b))
                    );
    
                //Console.Write("[a > b || a == b]:");
                //Console.WriteLine(a > b || a == b);
                MethodCallExpression call3 = Expression.Call(null, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }), Expression.Constant("[a > b || a == b]:"));
                MethodCallExpression call4 = Expression.Call(
                    null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(bool) }),
                    Expression.OrElse(Expression.Equal(a, b), Expression.GreaterThan(a, b))
                    );
    
                //Console.Write("[!(a == b)]:");
                //Console.WriteLine(!(a == b));
                MethodCallExpression call5 = Expression.Call(null, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }), Expression.Constant("[!(a == b)]:"));
                MethodCallExpression call6 = Expression.Call(
                    null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(bool) }),
                    Expression.Not(Expression.Equal(a, b))
                    );
                BlockExpression block = Expression.Block(
                    new ParameterExpression[] { a, b },
                    setA,
                    setB,
                    call1,
                    call2,
                    call3,
                    call4,
                    call5,
                    call6
                    );
    
                Expression<Action> lambda = Expression.Lambda<Action>(block);
                lambda.Compile()();
                Console.ReadKey();
    

    生成的表达式树如下

    .Lambda #Lambda1<System.Action>() {
        .Block(
            System.Int32 $a,
            System.Int32 $b) {
            $a = 10;
            $b = 11;
            .Call System.Console.Write("[a == b && a > b]:");
            .Call System.Console.WriteLine($a == $b && $a > $b);
            .Call System.Console.Write("[a > b || a == b]:");
            .Call System.Console.WriteLine($a == $b || $a > $b);
            .Call System.Console.Write("[!(a == b)]:");
            .Call System.Console.WriteLine(!($a == $b))
        }
    }
    

    四,位运算符

    &、|、^、~、<<、>>

    运算符 描述 实例
    & 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 (A & B) 将得到 12,即为 0000 1100
    | 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。 (A | B) 将得到 61,即为 0011 1101
    ^ 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 (A ^ B) 将得到 49,即为 0011 0001
    ~ 按位取反运算符是一元运算符,具有"翻转"位效果,即0变成1,1变成0,包括符号位。 (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。
    << 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 A << 2 将得到 240,即为 1111 0000
    >> 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 A >> 2 将得到 15,即为 0000 1111

    限于篇幅,就写示例了。

    位运算符 表达式树
    & Expression.Add(Expression left, Expression right)
    | Expression.Or(Expression left, Expression right)
    ^ Expression.ExclusiveOr(Expression expression)
    ~ Expression.OnesComplement( Expression expression)
    << Expression.LeftShift(Expression left, Expression right)
    >> Expression.RightShift(Expression left, Expression right)

    五,赋值运算符

    运算符 描述 实例
    = 简单的赋值运算符,把右边操作数的值赋给左边操作数 C = A + B 将把 A + B 的值赋给 C
    += 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A
    -= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
    *= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C *= A 相当于 C = C * A
    /= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
    %= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
    <<= 左移且赋值运算符 C <<= 2 等同于 C = C << 2
    >>= 右移且赋值运算符 C >>= 2 等同于 C = C >> 2
    &= 按位与且赋值运算符 C &= 2 等同于 C = C & 2
    ^= 按位异或且赋值运算符 C ^= 2 等同于 C = C ^ 2
    |= 按位或且赋值运算符 C |= 2 等同于 C = C | 2

    限于篇幅,请自行领略... ...

    运算符 表达式树
    = Expression.Assign
    += Expression.AddAssign
    -= Expression.SubtractAssign
    *= Expression.MultiplyAssign
    /= Expression.DivideAssign
    %= Expression.ModuloAssign
    <<= Expression.LeftShiftAssign
    >>= Expression.RightShiftAssign
    &= Expression.AndAssign
    ^= Expression.ExclusiveOrAssign
    |= Expression.OrAssign

    ^= ,注意有两种意思一种是位运算符的异或(ExclusiveOrAssign),一种是算术运算符的幂运算(PowerAssign)

    六,其他运算符

    运算符 描述 实例
    sizeof() 返回数据类型的大小。 sizeof(int),将返回 4.
    typeof() 返回 class 的类型。 typeof(StreamReader);
    & 返回变量的地址。 &a; 将得到变量的实际地址。
    * 变量的指针。 *a; 将指向一个变量。
    ? : 条件表达式 如果条件为真 ? 则为 X : 否则为 Y
    is 判断对象是否为某一类型。 If( Ford is Car) // 检查 Ford 是否是 Car 类的一个对象。
    as 强制转换,即使转换失败也不会抛出异常。 Object obj = new StringReader("Hello"); StringReader r = obj as StringReader;

    表达式树里面我没有找到这些运算符的如何编写,如果你找到了,欢迎告诉我。。。

  • 相关阅读:
    dubbo支持哪些通信协议?支持哪些序列化协议?
    spring常见面试题
    100道Java基础面试题收集整理(附答案)
    阿里面试题
    说一下的dubbo的工作原理?注册中心挂了可以继续通信吗?说说一次rpc请求的流程?
    为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗?
    layui增加转圈效果
    js防止重复提交代码
    工作流表介绍
    权限树的制作(menu)
  • 原文地址:https://www.cnblogs.com/whuanle/p/11545441.html
Copyright © 2011-2022 走看看