c#如何使用反射去创建一个委托_C# 反射的委托创建器

.Net 的反射是个很好很强大的东西,不过它的效率却实在是不给力。已经有很多人针对这个问题讨论过了,包括各种各样的 DynamicMethod 和各种各样的效率测试,不过总的来说解决方案就是利用 Expression Tree、Delegate.CreateDelegate 或者 Emit 构造出反射操作对应的委托,从而实现加速反射的目的。

虽然本篇文章同样是讨论利用委托来加速反射调用函数,不过重点并不在于如何提升调用速度,而是如何更加智能的构造出反射的委托,并最终完成一个方便易用的委托创建器 DelegateBuilder。

它的设计目标是:

能够对方法调用、构造函数调用,获取或设置属性和获取或设置字段提供支持。

能够构造出特定的委托类型,而不仅限于 Func 或者其它的 Func 和 Action,因为我个人很喜欢强类型的委托,同时类似 void MyDeleagte(params int[] args) 这样的委托有时候也是很有必要的,如果需要支持 ref 和 out 参数,就必须使用自定义的委托类型了。

能够支持泛型方法,因为利用反射选择泛型方法是件很纠结的事(除非没有同名方法),而且还需要再 MakeGenericMethod。

能够支持类型的显式转换,在对某些 private 类的实例方法构造委托时,实例本身就必须使用 object 传入才可以。

其中的 3、4 点,在前几篇随笔《C# 判断类型间能否隐式或强制类型转换》和《C# 泛型方法的类型推断》中已经被解决了,并且整合到了 PowerBinder 中,这里只要解决 1、2 点就可以了,这篇随笔就是来讨论如何根据反射来构造出相应的委托。

就目前完成的效果,DelegateBuilder 可以使用起来还是非常方便的,下面给出一些示例:

class Program {

public delegate void MyDelegate(params int[] args);

public static void TestMethod(int value) { }

public void TestMethod(uint value) { }

public static void TestMethod(params T[] arg) { }

static void Main(string[] args) {

Type type = typeof(Program);

Action m1 = type.CreateDelegate>("TestMethod");

m1(10);

Program p = new Program();

Action m2 = type.CreateDelegate>("TestMethod");

m2(p, 10);

Action m3 = type.CreateDelegate>("TestMethod");

m3(p, 10);

Action m4 = type.CreateDelegate>("TestMethod", p);

m4(10);

MyDelegate m5 = type.CreateDelegate("TestMethod");

m5(0, 1, 2);

}

}

可以说效果还是不错的,这里的 CreateDelegate 的用法与 Delegate.CreateDelegate 完全相同,功能却大大丰富,几乎可以只依靠 delegate type、type 和 memberName 构造出任何需要的委托,省去了自己反射获取类型成员的过程。

这里特别要强调一点:这个类用起来很简单,但是简单的背后是实现的复杂,所以各种没有发现的 bug 和推断错误是很正常的。

我再补充一点:虽然在这里我并不打算讨论效率问题,但的确有不少朋友对效率问题有点纠结,我就来详细解释下这个问题。

第一个问题:为什么要用委托来代替反射。如果手头有 Reflector 之类的反编译软件,可以看看 System.Reflection.RuntimeMethodInfo.Invoke 方法的实现,它首先需要检查参数(检查默认参数、类型转换之类的),然后检查各种 Flags,然后再调用 UnsafeInvokeInternal 完成真正的调用过程,显然比直接调用方法要慢上不少。而如果利用 Expression Tree 之类的方法构造出了委托,它就相当于只多了一层方法调用,性能不会损失多少(据说如果 Emit 用得好还能更快),因此才需要利用委托来代替反射。

第二个问题:什么时候适合用委托来代替反射。现在假设有一家公园,它的门票是 1 元,它还有一种终身票,票价是 20 元。如果我只是想进去看看,很可能以后就不再去了,那么我直接花 1 元进去是最合适的。但如果我想天天去溜达溜达,那么花 20 元买个终身票一定更加合适。

相对应的,1 元的门票就是反射,20 元的终身票就是委托——如果某个方法我只是偶尔调用一下,那么直接用反射就好了,反正损失也不是很大;如果我需要经常调用,花点时间构造个委托出来则是更好的选择,虽然构造委托这个过程比较慢,但它受用终身的。

第三个问题:怎么测试委托和反射的效率。测试效率的前提就是假设某个方法是需要被经常调用的,否则压根没必要使用委托。那么,基本的结构如下所示:

Stopwatch sw = new Stopwatch();

Type type = typeof(Program);

sw.Start();

Action action = type.CreateDelegate>("TestMethod");

for (int i = 0; i < 10000; i++)

{

action(i);

}

sw.Stop();

Console.WriteLine("DelegateBuilder:{0} ms", sw.ElapsedMilliseconds);

sw.Start();

MethodInfo method = type.GetMethod("TestMethod");

for (int i = 0; i < 10000; i++)

{

method.Invoke(null, new object[] { i });

}

sw.Stop();

Console.WriteLine("Reflection:{0} ms", sw.ElapsedMilliseconds);

这里将构造委托的过程和反射得到 MethodInfo 的过程都放在了循环的外面,是因为它们只需要获取一次,就可以一直使用的(也就是所谓的“预处理”)。至于时候将它们放在 StopWatch 的 Start 和 Stop 之间,就看是否想将预处理所需的时间也计算在内了。

目前我能想到的问题就这三个了,如果还有什么其它相关问题,可以联系我。

言归正传,下面就来分析如何为反射构造出相应的委托。为了简便起见,我将使用 Expression Tree 来构造委托,这样更加易读,而且效率也并不会比 Emit 低多少。对于 Expression 不熟悉的朋友可以参考 Expression 类。

一、从 MethodInfo 创建方法的委托

首先从创建方法的委托说开来,因为方法的委托显然是最常用、最基本的了。Delegate 类为我们提供了一个很好的参考,它的 CreateDelegate 方法有十个重载,这些重载之间的关系可以用下面的图表示出来,他们的详细解释可见 MSDN:

图1 Delegate.CreateDelegate

这些方法的确很给力,用起来也比较方便,尽管在我看来还不够强大:)。为了易于上手,自己的方法委托创建方法的行为也应该类似于 Delegate.CreateDelegate 方法,因此接下来会先分析 CreateDelegate 方法的用法,然后再解释如何自己创建委托。

1.1 创建开放的方法委托

CreateDelegate(Type, MethodInfo) 和 CreateDelegate(Type, MethodInfo, Boolean) 的功能是相同的,都是可以创建静态方法的委托,或者是显式提供实例方法的第一个隐藏参数(称开放的实例方法,从 .Net Framework 2.0 以后支持)的委托。以下面的类为例:

class TestClass {

public static void TestStaticMethod(string value) {}

public void TestMethod(string value) {}

}

要创建 TestStaticMethod 方法的委托,需要使用 Action 委托类型,代码为

Delegate.CreateDelegate(typeof(Action), type.GetMethod("TestStaticMethod"))

得到的委托的效果与 TestStaticMethod(arg1) 相同。

要创建 TestMethod 方法的委托,则需要使用 Action 委托类型才可以,第一个参数表示要在其上调用方法的 TestClass 的实例:

Delegate.CreateDelegate(typeof(Action), type.GetMethod("TestMethod"))

得到的委托的效果与 arg1.TestMethod(arg2) 相同。

这个方法的用法很明确,自己实现起来也非常简单:

首先对开放的泛型方法构造相应的封闭的泛型方法,做法与上一篇《C# 使用 Binder 类自定义反射》中的 2.2.2 处理泛型方法 一节使用的算法相同,这里就不再赘述了。

接下就可以直接利用 Expression.Call 创建一个方法调用的委托,并对每个参数添加一个强制类型转换(Expression.Convert)即可。需要注意的是如果 MethodInfo 是实例方法,那么第一个参数要作为实例使用。最后用 Expression 构造出来的方法应该类似于:

// method 对应于静态方法。

returnType MethodDelegate(PT0 p0, PT1 p1, ... , PTn pn) {

return method((T0)p0, (T1)p1, ... , (Tn)pn);

}

// method 对应于实例方法。

returnType MethodDelegate(PT0 p0, PT1 p1, ... , PTn pn) {

return ((T0)p0).method((T1)p1, ... , (Tn)pn);

}

构造开放的方法委托的核心方法如下所示:

private static Delegate CreateOpenDelegate(Type type,

MethodInfo invoke, ParameterInfo[] invokeParams,

MethodInfo method, ParameterInfo[] methodParams)

{

// 要求参数数量匹配,其中实例方法的第一个参数用作传递实例对象。

int skipIdx = method.IsStatic ? 0 : 1;

if (invokeParams.Length == methodParams.Length + skipIdx)

{

if (method.IsGenericMethodDefinition)

{

// 构造泛型方法的封闭方法,对于实例方法要跳过第一个参数。

Type[] paramTypes = GetParameterTypes(invokeParams, skipIdx, 0, 0);

method = method.MakeGenericMethodFromParams(methodParams, paramTypes);

if (method == null) { return null; }

methodParams = method.GetParameters();

}

// 方法的参数列表。

ParameterExpression[] paramList = GetParameters(invokeParams);

// 构造调用参数列表。

Expression[] paramExps = GetParameterExpressions(paramList, skipIdx, methodParams, 0);

if (paramExps != null)

{

// 调用方法的实例对象。

Expression instance = null;

if (skipIdx == 1)

{

instance = ConvertType(paramList[0], method.DeclaringType);

if (instance == null)

{

return null;

}

}

Expression methodCall = Expression.Call(instance, method, paramExps);

methodCall = GetReturn(methodCall, invoke.ReturnType);

if (methodCall != null)

{

return Expression.Lambda(type, methodCall, paramList).Compile();

}

}

}

return null;

}

1.2 创建第一个参数封闭的方法委托

CreateDelegate(Type, Object, MethodInfo) 和 CreateDelegate(Type, Object, MethodInfo, Boolean) 是最灵活的创建委托的方法,可以创建静态或实例方法的委托,可以提供或不提供第一个参数。先来给出所有用法的示例:

class TestClass {

public static void TestStaticMethod(string value) {}

public void TestMethod(string value) {}

}

对于 TestStaticMethod (静态方法)来说:

若 firstArgument 不为 null,则在每次调用委托时将其传递给方法的第一个参数,此时称为通过第一个参数封闭,要求委托的签名包括方法除第一个参数之外的所有参数,使用方法为

Delegate.CreateDelegate(typeof(Action), "str", type.GetMethod("TestStaticMethod"))

得到的委托的效果与 TestStaticMethod(firstArgument) 相同。

若 firstArgument 为 null,且委托和方法的签名匹配(即所有参数类型都兼容),则此时称为开放的静态方法委托,使用方法为

Delegate.CreateDelegate(typeof(Action), null, type.GetMethod("TestStaticMethod"))

得到的委托的效果与 TestStaticMethod(arg1) 相同。

若 firstArgument 为 null,且委托的签名以方法的第二个参数开头,其余参数类型都兼容,则此时称为通过空引用封闭的委托,使用方法为

Delegate.CreateDelegate(typeof(Action), null, type.GetMethod("TestStaticMethod"))

得到的委托的效果与 TestStaticMethod(null) 相同。

对于 TestMethod (实例方法)来说:

若 firstArgument 不为 null,则 firstArgument 被传递给隐藏的实例参数(就是 this),这时成为封闭的实例方法,要求委托的签名必须和方法的签名匹配,使用方法为

Delegate.CreateDelegate(typeof(Action), new TestClass(), type.GetMethod("TestMethod"))

得到的委托效果与 firstArgument.TestMethod(arg1) 相同。

若 firstArgument 为 null,且委托显示包含方法的第一个隐藏参数(就是 this),则此时称为开放的实例方法委托,使用方法为

Delegate.CreateDelegate(typeof(Action), null, type.GetMethod("TestMethod"))

得到的委托效果与 arg1.TestMethod(arg2) 相同。

若 firstArgument 为 null,且委托的签名与方法的签名匹配,则此时称为通过空引用封闭的委托,使用方法为

Delegate.CreateDelegate(typeof(Action), null, type.GetMethod("TestMethod"))

这种用法比较奇怪,这种用法类似于对空实例调用实例方法(null.TestMethod(obj)),在方法体内得到的 this 就是 null,在实际当中不是很有用。

将以上六点总结来看,就是根据方法是静态方法还是实例方法,以及委托与方法签名的匹配方式就可以决定如何构造委托了。下面就是判断的流程图:

图2 方法委托的流程图

对于开放的静态或实例方法,可以使用上一节完成的方法;对于封闭的静态或实例方法,做法也比较类似,只要将 firstArgument 作为静态方法的第一个参数或者是实例使用即可;在流程图中特地将通过空引用封闭的实例方法拿出来,是因为 Expression 不能实现对 null 调用实例方法,只能够使用 Delegate.CreateDelegate 来生成委托,然后在外面再套一层自己的委托以实现强制类型转换。这么做效率肯定会更低,但毕竟这种用法基本不可能见到,这里仅仅是为了保证与 CreateDelegate 的统一。

1.3 创建通用的方法委托

这里我多加了一个方法,就是创建一个通用的方法委托,这个委托的声明如下:

public delegate object MethodInvoker(object instance, params object[] parameters);

通过这个委托,就可以调用任意的方法了。要实现这个方法也很简单,只要用 Expression 构造出类似于下面的方法即可。

object MethodDelegate(object instance, params object[] parameters) {

// 检查 parameters 的长度。

if (parameters == null || parameters.Length != n + 1) {

throw new TargetParameterCountException();

}

// 调用方法。

return instance.method((T0)parameters[0], (T1)parameters[1], ... , (Tn)parameters[n]);

}

对于泛型方法,显然无法进行泛型参数推断,直接报错就好;对于静态方法,直接无视 instance 参数就可以。

public static MethodInvoker CreateDelegate(this MethodInfo method)

{

ExceptionHelper.CheckArgumentNull(method, "method");

if (method.IsGenericMethodDefinition)

{

// 不对开放的泛型方法执行绑定。

throw ExceptionHelper.BindTargetMethod("method");

}

// 要执行方法的实例。

ParameterExpression instanceParam = Expression.Parameter(typeof(object));

// 方法的参数。

ParameterExpression parametersParam = Expression.Parameter(typeof(object[]));

// 构造参数列表。

ParameterInfo[] methodParams = method.GetParameters();

Expression[] paramExps = new Expression[methodParams.Length];

for (int i = 0; i < methodParams.Length; i++)

{

// (Ti)parameters[i]

paramExps[i] = ConvertType(

Expression.ArrayIndex(parametersParam, Expression.Constant(i)),

methodParams[i].ParameterType);

}

// 静态方法不需要实例,实例方法需要 (TInstance)instance

Expression instanceCast = method.IsStatic ? null :

ConvertType(instanceParam, method.DeclaringType);

// 调用方法。

Expression methodCall = Expression.Call(instanceCast, method, paramExps);

// 添加参数数量检测。

methodCall = Expression.Block(GetCheckParameterExp(parametersParam, methodParams.Length), methodCall);

return Expression.Lambda(GetReturn(methodCall, typeof(object)),

instanceParam, parametersParam).Compile();

}

二、从 ConstructorInfo 创建构造函数的委托

创建构造函数的委托的情况就很简单了,构造函数没有静态和实例的区分,不存在泛型方法,而且委托和构造函数的签名一定是匹配的,实现起来就如同 1.1 创建开放的方法委托,不过这是用到的实 Expression.New 方法而不是 Expression.Call 了。

public static Delegate CreateDelegate(Type type, ConstructorInfo ctor, bool throwOnBindFailure)

{

ExceptionHelper.CheckArgumentNull(ctor, "ctor");

CheckDelegateType(type, "type");

MethodInfo invoke = type.GetMethod("Invoke");

ParameterInfo[] invokeParams = invoke.GetParameters();

ParameterInfo[] methodParams = ctor.GetParameters();

// 要求参数数量匹配。

if (invokeParams.Length == methodParams.Length)

{

// 构造函数的参数列表。

ParameterExpression[] paramList = GetParameters(invokeParams);

// 构造调用参数列表。

Expression[] paramExps = GetParameterExpressions(paramList, 0, methodParams, 0);

if (paramExps != null)

{

Expression methodCall = Expression.New(ctor, paramExps);

methodCall = GetReturn(methodCall, invoke.ReturnType);

if (methodCall != null)

{

return Expression.Lambda(type, methodCall, paramList).Compile();

}

}

}

if (throwOnBindFailure)

{

throw ExceptionHelper.BindTargetMethod("ctor");

}

return null;

}

与通用的方法委托类似的,我也使用下面的委托

public delegate object InstanceCreator(params object[] parameters);

来创建通用的构造函数的委托,与通用的方法委托的实现也很类似。

public static Delegate CreateDelegate(Type type, ConstructorInfo ctor, bool throwOnBindFailure)

{

ExceptionHelper.CheckArgumentNull(ctor, "ctor");

CheckDelegateType(type, "type");

MethodInfo invoke = type.GetMethod("Invoke");

ParameterInfo[] invokeParams = invoke.GetParameters();

ParameterInfo[] methodParams = ctor.GetParameters();

// 要求参数数量匹配。

if (invokeParams.Length == methodParams.Length)

{

// 构造函数的参数列表。

ParameterExpression[] paramList = GetParameters(invokeParams);

// 构造调用参数列表。

Expression[] paramExps = GetParameterExpressions(paramList, 0, methodParams, 0);

if (paramExps != null)

{

Expression methodCall = Expression.New(ctor, paramExps);

methodCall = GetReturn(methodCall, invoke.ReturnType);

if (methodCall != null)

{

return Expression.Lambda(type, methodCall, paramList).Compile();

}

}

}

if (throwOnBindFailure)

{

throw ExceptionHelper.BindTargetMethod("ctor");

}

return null;

}

三、从 PropertyInfo 创建属性的委托

有了创建方法的委托作为基础,创建属性的委托就非常容易了。如果委托具有返回值那么意味着是获取属性,不具有返回值(返回值为 typeof(void))意味着是设置属性。然后利用 PropertyInfo.GetGetMethod 或 PropertyInfo.GetSetMethod 来获取相应的 get 访问器或 set 访问器,最后直接调用创建方法的委托就可以了。

封闭的属性委托也同样很有用,这样可以将属性的实例与委托绑定。

对于属性并没有创建通用的委托,是因为属性的访问分为获取和设置两部分的,这两部分难以有效的结合到一块。

四、从 FieldInfo 创建字段的委托

在创建字段的委托时,就不能使用现有的方法了,而必须用 Expression.Assign 自己完成字段的赋值。字段的委托同样可以分为开放的字段委托和使用第一个参数封闭的字段委托,其判断过程如下:

图3 字段委托流程图

字段的处理很简单,就是通过 Expression.Field 访问字段,然后通过 Expression.Assign 对字段进行赋值,或者直接返回字段的值。图中单独列出来的“通过空引用封闭的实例字段”,同样是因为不能用代码访问空对象的实例字段,这显然是个毫无意义的操作,不过为了与通过空引用封闭的属性得到的结果相同,这里总是抛出 System.NullReferenceException。

五、从 Type 创建成员委托

这个方法提供了创建成员委托的最灵活的方式,它可以根据给出的成员名称、BindingFlags 和委托的签名决定是创建方法、构造函数、属性还是字段的委托。

它的做法就是,依次利用 PowerBinder.Cast 在 type 中查找与给定委托签名匹配的方法、属性和字段,并尝试为每个匹配的成员构造委托(使用前面四个部分中给出的方法)。当某个成员成功构造出委托,那么它就是最后需要的那个。

由于 PowerBinder 可以支持查找泛型方法和显式类型转换,因此构造委托的时候也自然就能够支持泛型方法和显式类型转换了。

DelegateBuilder 构造委托的方法算是到此结束了,完整的源代码可见 DelegateBuilder.cs,总共大约 2500 行,不过其中大部分都是注释和各种方法重载(目前有 54 个重载),VS 代码度量的结果只有 509 行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/367278.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BBS-登录

from django.db import models# Create your models here. from django.contrib.auth.models import AbstractUser#用户 class UserInfo(AbstractUser):nidmodels.AutoField(primary_keyTrue)telephonemodels.CharField(max_length32)avatarmodels.FileField(upload_toavatar/,…

html 输入框从左上角,在输入框的左上角,使文本开始_input_开发99编程知识库

網頁上有幾個輸入框&#xff0c;我希望文本從左上角開始。 目前&#xff0c;使用 below&#xff0c;它的左對齊&#xff0c;但在框的中間。 我嘗試了垂直對齊和其他的東西&#xff0c;但沒有。 我不想使用 padding&#xff0c;因為文本需要包圍&#xff0c;它只能使它彈出框頂部…

PHP定时任务Crontab结合CLI模式详解

从版本 4.3.0 开始&#xff0c;PHP 提供了一种新类型的 CLI SAPI&#xff08;Server Application Programming Interface&#xff0c;服务端应用编程端口&#xff09;支持&#xff0c;名为 CLI&#xff0c;意为 Command Line Interface&#xff0c;即命令行接口。 STDIN 标准输…

使用Mockito和BeanPostProcessors在Spring注入测试双打

我非常确定&#xff0c;如果您曾经使用过Spring并且熟悉单元测试&#xff0c;那么您会遇到与您不想修改的Spring应用程序上下文中注入模拟/间谍&#xff08;测试双打&#xff09;有关的问题。 本文介绍了一种使用Spring组件解决此问题的方法。 项目结构 让我们从项目结构开始&…

当面试官问你如何进行性能优化时,你该这么回答(一)

背景 在开发好页面后&#xff0c;如何让页面更快更好的运行&#xff0c;是区分一个程序猿技术水平和视野的一个重要指标。所以面试时&#xff0c;面试官总会问你一个问题&#xff0c;如何进行性能优化呢&#xff1f; 如果你这时是头脑一片空白&#xff0c;或是像之前的我一样…

二叉搜索时与双向链表python_JZ26-二叉搜索树与双向链表

1、中序遍历&#xff0c;当前结点&#xff0c;以及左侧排好序的双向链表&#xff0c;再调整当前结点的指针指向最前结点/* struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {} };*/ class Solution …

dva使用心得

组件在异步数据到来前已经完成初始化&#xff0c;是导致constructor中无法取到所需数据的原因。所以最直接的方法&#xff0c;就是修改组件创建的时间 //把原来的组件内部控制显示/* <ComponentName show{modelName.show}/> *///更改为状态直接控制组件是否存在{ modelN…

html右缩进怎么设置,WPS中怎么设置右缩进两个字符?

回答&#xff1a;打开我们的Word文档&#xff0c;调整好我们的文字内容&#xff0c;然后全选我们的文字内容&#xff0c;注意要分段时按下键盘上的回车键另起一行。请点击输入图片描述接着&#xff0c;我们点击顶部菜单栏的“开始”菜单&#xff0c;在开始菜单下面的子菜单中找…

VS2013专业版+QT5.6.3+qt-vs-addin-1.2.5环境搭建

一、工具资料&#xff1a; 1.vs2013专业版地址&#xff1a;http://download.csdn.net/download/u010368556/10238145 2.qt各版本地址&#xff1a;http://download.qt.io/archive/qt/ 3.qt-vs插件地址&#xff1a;http://download.qt.io/archive/vsaddin/ 二、环境搭建过程&…

不到50行代码实现一个能对请求并发数做限制的通用RequestDecorator

使用场景 在开发中&#xff0c;我们可能会遇到一些对异步请求数做并发量限制的场景&#xff0c;比如说微信小程序的request并发最多为5个&#xff0c;又或者我们需要做一些批量处理的工作&#xff0c;可是我们又不想同时对服务器发出太多请求&#xff08;可能会对服务器造成比…

使用ActiveMQ和HornetQ通过WebSocket通过STOMP轻松进行消息传递

消息传递是用于构建不同级别的分布式软件系统的极其强大的工具。 通常&#xff0c;至少在Java生态系统中&#xff0c;客户端&#xff08;前端&#xff09;从不直接与消息代理&#xff08;或交换&#xff09;进行交互&#xff0c;而是通过调用服务器端&#xff08;后端&#xff…

yum 安装mysql的位置_Yum安装MySQL以及相关目录路径和修改目录

有些时候,为了方便,有些同学喜欢通过yum的方式安装MySQL,没有设置统一的文件目录以及软件目录,那么就会为后续的维护工作带来很大的麻烦&#xff01;下面就简单介绍一下yum安装MySQL的步骤以及这类安装下的相关目录路径,最后简单介绍下如何更改文件目录&#xff01;YUM安装MySQ…

【laravel】【转发】laravel 导入导出excel文档

1、简介 Laravel Excel 在 Laravel 5 中集成 PHPOffice 套件中的 PHPExcel &#xff0c;从而方便我们以优雅的、富有表现力的代码实现Excel/CSV文件的导入和 导出 。 该项目的GitHub地址是&#xff1a; https://github.com/Maatwebsite/Laravel-Excel 。 本文我们将在Laravel中…

javaScript--DOM

一、JavaScript JavaScript这门语言由 DOM、BOM、ECMAScript 组成。 DOM&#xff1a;document object model 文档对象模型。体现在代码中就是 document 对象。 BOM&#xff1a;browser object model 浏览器对象模型。体现在代码中就是window对象。 ECMA&#xff1a;核心语法。包…

计算机原理及应用课程,课程介绍

《计算机原理及应用》课程的计划学时为70学时&#xff0c;其中课堂授课为58学时&#xff0c;课程实验为12学时。另外&#xff0c;还设置了2周课程综合性实验。三、教学内容(一)理论教学《计算机原理及应用》的课程内容共分为9部分&#xff0c;其具体内容、知识点和课时分配如下…

你真的了解css像素嘛?

在日常开发中&#xff0c;px一定是大家接触过最多的css单位&#xff0c;但是你真的了解px嘛&#xff1f;1px在屏幕中到底是多大呢&#xff1f;另外不知道大家有没有过下面这些疑惑: 为什么一个元素在pc上和移动端的物理尺寸不一样&#xff0c;但是两者的视觉效果上却差不多呢&…

Apache Camel 2.12 –支持后退,以减少较积极的轮询路线

这是另一篇博客文章&#xff0c;介绍了下一个Apache Camel 2.12版本中即将进行的改进和新功能。 在上一个博客中&#xff0c;我谈到了路由直接支持的cron表达式 。 这篇博客文章与之相关&#xff0c;因为我们对轮询路由具有另一个新功能&#xff08;使用计划的轮询使用者&…

django框架概述

------------------MVC与MVT框架------------------- 1、MVCMVC框架的核心思想是&#xff1a;解耦。降低各功能模块之间的耦合性&#xff0c;方便将来变化时&#xff0c;更容易重构代码&#xff0c;最大程度上实现代码的重用。m表示model&#xff0c;重要用于对数据层的封装&am…

小鬼难缠--python小bug备忘

今天编译pyhon做人脸识别&#xff0c;遇到几个问题&#xff0c;做个记录吧。 编译报错&#xff1a; File "harrClassifier.py", line 17, in <module> flagscv2.CV_HAAR_SCALE_IMAGEAttributeError: module object has no attribute CV_HAAR_SCALE_IMAGE 定位根…

mysql for mac中文_mysql for Mac 下创建数据表中文显示为?的解决方法

在我的绝版Mac mini下安装了mysql 5.7版本&#xff0c;实例中&#xff0c;在通过load data 导入数据时发现表中的中文显示为 &#xff1f;通过百度&#xff0c;发现多个版本的解决方法&#xff0c;将其中一个成功解决的方法贴上来&#xff1a;大多方法都是这样&#xff1a;需要…