zoukankan      html  css  js  c++  java
  • C# 表达式树 Expression Trees知识总结

    C# 知识回顾 - 表达式树 Expression Trees

    目录

    简介

      表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。

      你可以对表达式树中的代码进行编辑和运算。这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。 

      表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性。 

    一、Lambda 表达式创建表达式树

      若 lambda 表达式被分配给 Expression<TDelegate> 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。  

      C# 编译器只能从表达式 lambda (或单行 lambda)生成表达式树。 

      下列代码示例使用关键字 Expression创建表示 lambda 表达式:

    1             Expression<Action<int>> actionExpression = n => Console.WriteLine(n);
    2             Expression<Func<int, bool>> funcExpression1 = (n) => n < 0;
    3             Expression<Func<int, int, bool>> funcExpression2 = (n, m) => n - m == 0;

    二、API 创建表达式树

      通过 API 创建表达式树需要使用 Expression 类

      下列代码示例展示如何通过 API 创建表示 lambda 表达式:num => num == 0

    复制代码
    1             //通过 Expression 类创建表达式树
    2             //  lambda:num => num == 0
    3             ParameterExpression pExpression = Expression.Parameter(typeof(int));    //参数:num
    4             ConstantExpression cExpression = Expression.Constant(0);    //常量:0
    5             BinaryExpression bExpression = Expression.MakeBinary(ExpressionType.Equal, pExpression, cExpression);   //表达式:num == 0
    6             Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(bExpression, pExpression);  //lambda 表达式:num => num == 0
    复制代码

      代码使用 Expression 类的静态方法进行创建。

    三、解析表达式树 

       下列代码示例展示如何分解表示 lambda 表达式 num => num == 0 的表达式树。

    复制代码
    1             Expression<Func<int, bool>> funcExpression = num => num == 0;
    2 
    3             //开始解析
    4             ParameterExpression pExpression = funcExpression.Parameters[0]; //lambda 表达式参数
    5             BinaryExpression body = (BinaryExpression)funcExpression.Body;  //lambda 表达式主体:num == 0
    6 
    7             Console.WriteLine($"解析:{pExpression.Name} => {body.Left} {body.NodeType} {body.Right}");
    复制代码

    四、表达式树永久性

      表达式树应具有永久性(类似字符串)。这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。  你可以使用表达式树访问者遍历现有表达式树。第七节介绍了如何修改表达式树。
     

    五、编译表达式树

      Expression<TDelegate> 类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。

    复制代码
    1             //创建表达式树
    2             Expression<Func<string, int>> funcExpression = msg => msg.Length;
    3             //表达式树编译成委托
    4             var lambda = funcExpression.Compile();
    5             //调用委托
    6             Console.WriteLine(lambda("Hello, World!"));
    7 
    8             //语法简化
    9             Console.WriteLine(funcExpression.Compile()("Hello, World!"));
    复制代码

    六、执行表达式树

      执行表达式树可能会返回一个值,也可能仅执行一个操作(例如调用方法)。

      只能执行表示 lambda 表达式的表达式树。表示 lambda 表达式的表达式树属于 LambdaExpression 或 Expression<TDelegate> 类型。若要执行这些表达式树,需要调用 Compile 方法来创建一个可执行委托,然后调用该委托。
    复制代码
     1             const int n = 1;
     2             const int m = 2;
     3 
     4             //待执行的表达式树
     5             BinaryExpression bExpression = Expression.Add(Expression.Constant(n), Expression.Constant(m));
     6             //创建 lambda 表达式
     7             Expression<Func<int>> funcExpression = Expression.Lambda<Func<int>>(bExpression);
     8             //编译 lambda 表达式
     9             Func<int> func = funcExpression.Compile();
    10 
    11             //执行 lambda 表达式
    12             Console.WriteLine($"{n} + {m} = {func()}");
    复制代码

    七、修改表达式树 

       该类继承 ExpressionVisitor 类,通过 Visit 方法间接调用 VisitBinary 方法将 != 替换成 ==。基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。  

    复制代码
     1     internal class Program
     2     {
     3         private static void Main(string[] args)
     4         {
     5             Expression<Func<int, bool>> funcExpression = num => num == 0;
     6             Console.WriteLine($"Source: {funcExpression}");
     7 
     8             var visitor = new NotEqualExpressionVisitor();
     9             var expression = visitor.Visit(funcExpression);
    10 
    11             Console.WriteLine($"Modify: {expression}");
    12 
    13             Console.Read();
    14         }
    15 
    16         /// <summary>
    17         /// 不等表达式树访问器
    18         /// </summary>
    19         public class NotEqualExpressionVisitor : ExpressionVisitor
    20         {
    21             public Expression Visit(BinaryExpression node)
    22             {
    23                 return VisitBinary(node);
    24             }
    25 
    26             protected override Expression VisitBinary(BinaryExpression node)
    27             {
    28                 return node.NodeType == ExpressionType.Equal
    29                     ? Expression.MakeBinary(ExpressionType.NotEqual, node.Left, node.Right) //重新弄个表达式:用 != 代替 ==
    30                     : base.VisitBinary(node);
    31             }
    32         }
    33     }
    复制代码

    八、调试

      8.1 参数表达式

    1             ParameterExpression pExpression1 = Expression.Parameter(typeof(string));
    2             ParameterExpression pExpression2 = Expression.Parameter(typeof(string), "msg");

    图8-1

    图8-2

       从 DebugView 可知,如果参数没有名称,则会为其分配一个自动生成的名称。

    1             const int num1 = 250;
    2             const float num2 = 250;
    3 
    4             ConstantExpression cExpression1 = Expression.Constant(num1);
    5             ConstantExpression cExpression2 = Expression.Constant(num2);

    图8-3

    图8-4

       从 DebugView 可知,float 比 int 多了个后缀 F。

    1             Expression lambda1 = Expression.Lambda<Func<int>>(Expression.Constant(250));
    2             Expression lambda2 = Expression.Lambda<Func<int>>(Expression.Constant(250), "CustomName", null);

    图8-5

    图8-6

       观察 DebugView ,如果 lambda 表达式没有名称,则会为其分配一个自动生成的名称。

    出处:https://www.cnblogs.com/liqingwen/p/5868688.html

    =======================================================================================

    Expression表达式树

    表达式树表示树状数据结构的代码,树状结构中的每个节点都是一个表达式,例如一个方法调用或类似 x < y 的二元运算

    1.利用 Lambda 表达式创建表达式树

    1
    Expression<Func<int, int, int, int>> expr = (x, y, z) => (x + y) / z;

    2.编译表达式树,该方法将表达式树表示的代码编译成一个可执行委托

    1
    expr.Compile()(1, 2, 3)

    3.IQueryable<T>的扩展方法,WhereIn的实现

    1
    var d = list.AsQueryable().WhereIn(o => o.Id1, new int[] { 1, 2 });

     完整代码:

    复制代码
    using MongoDB.Bson;
    using MongoDB.Driver;
    using MongoDB.Driver.Builders;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MongoDBTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                //使用LambdaExpression构建表达式树
                Expression<Func<int, int, int, int>> expr = (x, y, z) => (x + y) / z;
                Console.WriteLine(expr.Compile()(1, 2, 3));
    
                //使用LambdaExpression构建可执行的代码
                Func<int, int, int, int> fun = (x, y, z) => (x + y) / z;
                Console.WriteLine(fun(1, 2, 3));
    
                //动态构建表达式树
                ParameterExpression pe1 = Expression.Parameter(typeof(int), "x");
                ParameterExpression pe2 = Expression.Parameter(typeof(int), "y");
                ParameterExpression pe3 = Expression.Parameter(typeof(int), "z");
                var body = Expression.Divide(Expression.Add(pe1, pe2), pe3);
                var w = Expression.Lambda<Func<int, int, int, int>>(body, new ParameterExpression[] { pe1, pe2, pe3 });
                Console.WriteLine(w.Compile()(1, 2, 3));
    
                List<Entity> list = new List<Entity> { new Entity { Id1 = 1 }, new Entity { Id1 = 2 }, new Entity { Id1 = 3 } };
    
                var d = list.AsQueryable().WhereIn(o => o.Id1, new int[] { 1, 2 });
                d.ToList().ForEach(o =>
                {
                    Console.WriteLine(o.Id1);
                });
    
                Console.ReadKey();
    
            }
    
    
        }
        public class Entity
        {
            public ObjectId Id;
            public int Id1;
            public string Name { get; set; }
        }
    
        public static class cc
        {
    
    
            public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query, Expression<Func<T, TValue>> obj, IEnumerable<TValue> values)
            {
                return query.Where(BuildContainsExpression(obj, values));
            }
    
            private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
            {
                if (null == valueSelector)
                {
                    throw new ArgumentNullException("valueSelector");
                }
                if (null == values)
                {
                    throw new ArgumentNullException("values");
                }
                var p = valueSelector.Parameters.Single();
                if (!values.Any()) return e => false;
    
                var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
                var body = equals.Aggregate(Expression.Or);
    
                return Expression.Lambda<Func<TElement, bool>>(body, p);
            }
        }
    }
    复制代码

     参考博客:

    1. C#中的表达式树
    2. 表达式树基础
    3. C#高级程序设计(九)——表达式树
    4. 表达式树MSDN

    出处:https://www.cnblogs.com/LittleFeiHu/p/4050428.html

    =======================================================================================

    C#中的表达式树

        本人之前从未接触过表达式树的概念,所以特意从网上找到两篇这方面的资料学习了下。本文为阅读笔记性质博客!

        表达式树是.NET 3.5之后引入的,它是一个强大灵活的工具(比如用在LINQ中构造动态查询)。

        先来看看Expression类的API接口:

    复制代码
    using System.Collections.ObjectModel;
     
    namespace System.Linq.Expressions
    {
        // Summary:
        //     Represents a strongly typed lambda expression as a data structure in the
        //     form of an expression tree. This class cannot be inherited.
        //
        // Type parameters:
        //   TDelegate:
        //     The type of the delegate that the System.Linq.Expressions.Expression<tdelegate>
        //     represents.
        public sealed class Expression<tdelegate> : LambdaExpression
        {
            // Summary:
            //     Compiles the lambda expression described by the expression tree into executable
            //     code.
            //
            // Returns:
            //     A delegate of type TDelegate that represents the lambda expression described
            //     by the System.Linq.Expressions.Expression<tdelegate>.
            public TDelegate Compile();
        }
    }
    复制代码

        表达式树的语法如下:

    Expression<Func<type,returnType>> = (param) => lamdaexpresion;

        我们先来看一个简单例子:

    Expression<Func<int, int, int>> expr = (x, y) => x+y;

        这就是一个表达式树了。使用Expression Tree Visualizer工具(直接调试模式下看也可以,只不过没这个直观)在调试模式下查看这个表达式树(就是一个对象),如下:

    exp_tree

        可以看到表达式树主要由下面四部分组成:

    1、Body 主体部分
    2、Parameters 参数部分
    3、NodeType 节点类型
    4、Lambda表达式类型

        对于前面举的例子,主体部分即x+y,参数部分即(x,y)。Lambda表达式类型是Func<Int32, Int32, Int32>。注意主体部分可以是表达式,但是不能包含语句,如下这样:

    Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };
         会报编译错误“Lambada expression with state body cannot be converted to expression tree”:即带有语句的Lambda表达式不能转换成表达式树。

        用前面的方法虽然可以创建表达式树,但是不够灵活,如果要灵活构建表达式树,可以像下面这样:

    ParameterExpression exp1 = Expression.Parameter(typeof(int), "a");
    ParameterExpression exp2 = Expression.Parameter(typeof(int), "b");
    
    BinaryExpression exp = Expression.Multiply(exp1,exp2);
    var lamExp = Expression.Lambda<Func<int, int, int>>(exp, new ParameterExpression[] { exp1, exp2 });

        exp1、exp2即表达式树的参数,exp是表达式树的主体。如果我利用Reflector反编译Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };得到下面的C#代码:

    ParameterExpression CS$0$0000;
    ParameterExpression CS$0$0001;
    Expression<Func<int, int, int>> expr = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(CS$0$0000 = Expression.Parameter(typeof(int), "x"), CS$0$0001 = Expression.Parameter(typeof(int), "y")), new ParameterExpression[] { CS$0$0000, CS$0$0001 });

        可以看到它基本和上面的手动构建代码一致。再来看一个简单的例子:

    Expression<Func<Customer, bool>> filter =
        cust => Equal(Property(cust,"Region"),"North");

        可以用下面的代码手动构建效果等同于上面的表达式树:

    复制代码
    // declare a parameter of type Customer named cust
    
    ParameterExpression custParam = Expression.Parameter(
    
        typeof(Customer), "custParam");
    
    // compare (equality) the Region property of the
    
    // parameter against the string constant "North"
    
    BinaryExpression body = Expression.Equal(
    
        Expression.Property(custParam, "Region"),
    
        Expression.Constant("North", typeof(string)));
    
    // formalise this as a lambda
    
    Expression<Func<Customer, bool>> filter =
    
        Expression.Lambda<Func<Customer, bool>>(body, cust);
    复制代码

        然后我们可以通过表达式树的Compile方法将表达式树编译成Lambda表达式,如下:

    Func<Customer, bool> filterFunc = filter.Compile();
        

        但是Compile调用过程涉及动态代码生成,所以出于性能考虑最好只调用一次,然后缓存起来。或者像下面这样在静态构造块中使用(也只会调用一次):

    复制代码
    public static class Operator<T>
    {
        private static readonly Func<T, T, T> add;
        public static T Add(T x, T y)
        {
            return add(x, y);
        }
        static Operator()
        {
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");
            var body = Expression.Add(x, y);
            add = Expression.Lambda<Func<T, T, T>>(
                body, x, y).Compile();
        }
    }
    复制代码
        

        Expression类包含下面几类静态方法(.NET 3.5中):

    复制代码
    Arithmetic: Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power, 
    Subtract, SubtractChecked, UnaryPlus
    
    Creation: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInit
    
    Bitwise: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>)
    
    Logical: AndAlso (&&), Condition (? :), Equal, GreaterThan, GreaterThanOrEqual, LessThan, 
    LessThanOrEqual, NotEqual, OrElse (||), TypeIs
    
    Member Access: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrField
    
    Other: Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Parameter, TypeAs, Quote
    复制代码

        下面我们类似前面重载一个浅拷贝的例子(比使用反射开销小):

    复制代码
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace ExpressionTreeLab
    {
        class Program
        {
            static void Main(string[] args)
            {
                var p = new Person()
                    {
                        Name = "jxq",
                        Age = 23
                    };
                var shallowCopy = Operator<Person>.ShallowCopy(p);
                shallowCopy.Name = "feichexia";
                Console.WriteLine(shallowCopy.Name);
                Console.WriteLine(p.Name);
    
                Console.ReadKey();
            }
    
            public class Person
            {
                public string Name { get; set; }
                public int Age { get; set; }
            }
    
            public static class Operator<T>
            {
                private static readonly Func<T, T> ShallowClone; 
    
                public static T ShallowCopy(T sourcObj)
                {
                    return ShallowClone.Invoke(sourcObj);
                }
    
                static Operator()
                {
                    var origParam = Expression.Parameter(typeof(T), "orig");
    
                    // for each read/write property on T, create a  new binding 
                    // (for the object initializer) that copies the original's value into the new object 
                    var setProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                            where prop.CanRead && prop.CanWrite
                                            select (MemberBinding)Expression.Bind(prop, Expression.Property(origParam, prop));
    
                    var body = Expression.MemberInit( // object initializer 
                        Expression.New(typeof(T)), // ctor 
                        setProps // property assignments 
                    );
    
                    ShallowClone = Expression.Lambda<Func<T, T>>(body, origParam).Compile();
                }
            }
        }
    }
    复制代码

        继续看Expression.AndAlso的使用,它可以用来替代类似下面这种多条件与的情况:

    Func<Person, Person, bool> personEqual = (person1, person2) => person1.Name == person2.Name && person1.Age == person2.Age;
     if(personEqual(p1, p2))
    {
        Console.WriteLine("两个对象所有属性值都相等!");
    }

        代码如下:

    复制代码
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace ExpressionTreeLab
    {
        class Program
        {
            static void Main(string[] args)
            {
                var p1 = new Person()
                    {
                        Name = "jxq",
                        Age = 23
                    };
                var p2 = new Person()
                    {
                        Name = "jxq",
                        Age = 23
                    };
    
                if (Operator<Person>.ObjectPropertyEqual(p1, p2))
                {
                    Console.WriteLine("两个对象所有属性值都相等!");
                }
                
                Console.ReadKey();
            }
    
            public class Person
            {
                public string Name { get; set; }
                public int Age { get; set; }
            }
    
            public static class Operator<T>
            {
                private static readonly Func<T, T, bool> PropsEqual; 
    
                public static bool ObjectPropertyEqual(T obj1, T obj2)
                {
                    return PropsEqual.Invoke(obj1, obj2);
                }
    
                static Operator()
                {
                    var x = Expression.Parameter(typeof(T), "x");
                    var y = Expression.Parameter(typeof(T), "y");
    
                    // 获取类型T上的可读Property
                    var readableProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                            where prop.CanRead
                                            select prop;
    
                    Expression combination = null;
                    foreach (var readableProp in readableProps)
                    {
                        var thisPropEqual = Expression.Equal(Expression.Property(x, readableProp),
                                                             Expression.Property(y, readableProp));
    
                        if(combination == null)
                        {
                            combination = thisPropEqual;
                        }
                        else
                        {
                            combination = Expression.AndAlso(combination, thisPropEqual);
                        }
                    }
    
                    if(combination == null)   // 如果没有需要比较的东西,直接返回false
                    {
                        PropsEqual = (p1, p2) => false;
                    }
                    else
                    {
                        PropsEqual = Expression.Lambda<Func<T, T, bool>>(combination, x, y).Compile();
                    }
                }
            }
        }
    }
    复制代码

        在.NET 4.0中扩展了一些Expression的静态方法,使得编写动态代码更容易:

    复制代码
    Mutation: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign, RightShiftAssign, SubtractAssign, SubtractAssignChecked
    
    Arithmetic: Decrement, Default, Increment, OnesComplement
    
    Member Access: ArrayAccess, Dynamic
    
    Logical: ReferenceEqual, ReferenceNotEqual, TypeEqual
    
    Flow: Block, Break, Continue, Empty, Goto, IfThen, IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, Variable
    
    Exceptions: Catch, Rethrow, Throw
    
    Debug: ClearDebugInfo, DebugInfo
    复制代码

        下面是一个利用表达式树编写动态代码的例子(循环打印0到9):

    复制代码
    using System;
    using System.Linq.Expressions;
    
    namespace ExpressionTreeLab
    {
        class Program
        {
            static void Main(string[] args)
            {
                var exitFor = Expression.Label("exitFor"); // jump label
                var x = Expression.Variable(typeof(int), "x");
                var body = 
                    Expression.Block(
                        new[] { x }, // declare scope variables
                        Expression.Assign(x, Expression.Constant(0, typeof(int))), // init
                        Expression.Loop(
                            Expression.IfThenElse(
                                Expression.GreaterThanOrEqual( // test for exit
                                    x,
                                    Expression.Constant(10, typeof(int))
                                ),
                                Expression.Break(exitFor), // perform exit
                                Expression.Block( // perform code
                                    Expression.Call(
                                        typeof(Console), "WriteLine", null, x),
                                    Expression.PostIncrementAssign(x)
                                )
                            ), exitFor
                         )  // Loop ends
                     );
    
                var runtimeLoop = Expression.Lambda<Action>(body).Compile();
                runtimeLoop();
    
                Console.Read();
            }
    
        }
    }
    复制代码

        另外WhereIn扩展实现如下,如果前面的例子都熟悉了的话,这个自然也很容易看懂了:

    复制代码
        /// <summary>
        ///   使之支持Sql in语法
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <typeparam name = "TValue"></typeparam>
        /// <param name = "query"></param>
        /// <param name = "obj"></param>
        /// <param name = "values"></param>
        /// <returns></returns>
        public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query, Expression<Func<T, TValue>> obj, IEnumerable<TValue> values)
        {
            return query.Where(BuildContainsExpression(obj, values));
        }
    
        private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
            Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
        {
            if (null == valueSelector)
            {
                throw new ArgumentNullException("valueSelector");
            }
            if (null == values)
            {
                throw new ArgumentNullException("values");
            }
            var p = valueSelector.Parameters.Single();
            if (!values.Any()) return e => false;
    
            var equals = values.Select(value => (Expression) Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof (TValue))));
            var body = equals.Aggregate(Expression.Or);
            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }
    复制代码

        调用方式如下:

    db.Users.WhereIIn(u => u.Id, new int[] { 1, 2, 3 });

        关于使用表达式树构建LINQ动态查询,请参考Dynamic Linq Queries with Expression Trees

    参考资料:

    http://www.codeproject.com/Tips/438804/Expression-Tree

    http://www.infoq.com/articles/expression-compiler

    出处:https://www.cnblogs.com/feichexia/archive/2013/05/28/3104832.html

    您的资助是我最大的动力!
    金额随意,欢迎来赏!
    款后有任何问题请给我留言。

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
    如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的关注我。(●'◡'●)

    如果你觉得本篇文章对你有所帮助,请给予我更多的鼓励,求打             付款后有任何问题请给我留言!!!

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【Jack_孟】!

  • 相关阅读:
    golang中,new和make的区别
    k8s客户端库
    k8s 拉取私有镜像
    kubernetes-client / python
    k8s集群外go客户端示例
    K8s获取NodePort
    KUBERNETES中的服务发现机制与方式
    Rancher容器目录持久化
    rancher k8s 实现pod弹性伸缩
    在Terminal里,使用Shift+Insert来代替鼠标右键来进行粘贴操作
  • 原文地址:https://www.cnblogs.com/mq0036/p/15103164.html
Copyright © 2011-2022 走看看