跟随.NET 4.0脚步.docx_第1页
跟随.NET 4.0脚步.docx_第2页
跟随.NET 4.0脚步.docx_第3页
跟随.NET 4.0脚步.docx_第4页
跟随.NET 4.0脚步.docx_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

dynamic:用于生成一个动态的类型,在代码中可以通过不同的实例化类来变成不同的对象,例如:注:对于dynamic类型有两个限制。动态对象不支持扩展方法,匿名函数(Lambda表达式)也不能用作动态方法调用的参数,因此LINQ不能用于动态对象。大多数LINQ调用都是扩展方法,而Lambda表达式用作这些扩展方法的参数。dynamic实质:C#编译器做了很多工作,以支持动态类型。在生成的代码中,会看到对System.Runtime.CompilerServices.CallSite和System.Runtime.CompilerServices.CallSiteBinder类的引用。CallSite是在运行期间处理查找操作的类型。在运行期间调用动态对象时,必须找到该对象,看看其成员是否存在。CallSite会缓存这个信息,这样查找操作就不需要重复执行。没有这个过程,循环结构的性能就有问题。CallSite完成了成员查找操作后,就调用CallSiteBinder()方法。它从CallSite中提取信息,并生成表达式树,来表示绑定器绑定的操作。显然这需要做许多工作。优化非常复杂的操作时需要格外小心。显然,使用dynamic类型是有用的,但它是有代价的。dynamic VS object:很多情况下,初见dynamic会觉得和System.object有很多表现上的相似性。然而,System.object毕竟是静态类型;而dynamic要解决的是对动态对象的绑定。例如一个反射对象、一个DOM实例、一个COM对象或者一个来自IronRuby语言或其他语言的对象。DLR ScriptRuntime:假定能给应用程序添加脚本编辑功能,并给脚本传入数值和从脚本传出数值,使应用程序可以利用脚本完成工作。这些都是在应用程序中包含DLR的ScriptRuntime而提供的功能。目前,IronRuby、IronPython和JavaScript都支持包含在应用程序中的脚本语言。有了ScriptRuntime,就可以执行存储在文件中的代码段或完整的脚本。可以选择合适的语言引擎,或者让DLR确定使用什么引擎。脚本可以在自己的应用程序域或者在当前的应用程序域中创建。不仅可以给脚本传入数值并从脚本中传出数值,还可以在脚本中调用在动态对象上创建的方法。下面是一个Windows客户端应用程序,它也可以是一个大型Web应用程序或任何其他应用程序。下图显示了这个应用程序的样例。该应用程序提取所购买的物品数量和物品的总价,并根据所选的单选按钮使用某个折扣。在实际的应用程序中,系统使用略微复杂的方式确定要使用的折扣,但对于本例,单选按钮就足够了。下面是计算折扣的代码:第一部分仅确定要应用折扣的脚本AmountDisc.py或CoundDisc.py。AmountDisc.py根据购买的金额计算折扣。能打折的最低购买金额是$25.00.如果购买金额小于这个值,就不计算折扣,否则就使用2.5%的折扣率。CountDisc.py根据购买的物品数量计算折扣:在这个Python脚本中,购买的物品数量必须大于5,才能给总价应用10%的折扣率。下一部分是启动ScriptRuntime环境。这需要执行4个特定的步骤:创建ScriptRuntime对象、设置合适的ScriptEngine和创建ScriptSource,以及创建ScriptScope。ScriptRuntime对象是起点,也是包含ScriptRuntime的基础。它拥有包含环境的全局状态。ScriptRuntime对象使用CreateFromConfiguration()静态方法创建。app.config如下所示:这段代码定义了“Microsoft.scripting”的一部分,设置了IronPython语言引擎的几个属性。接着,从ScriptRuntime中获取一个对ScriptEngine的引用。在本例中,指定需要Python引擎,但ScriptRuntime可以自己确定这一点,因为脚本的扩展名是py。ScriptRuntime完成了执行脚本代码的工作。执行文件或代码段中的脚本有几种方法。ScriptEngine还提供了ScriptSource和ScriptScope。ScriptSource对象允许访问脚本,它表示脚本的源代码。有了它,就可以操作脚本的源代码。从磁盘上加载它,逐行解析它,甚至把脚本编译到CompiledCode对象中。如果多次执行同一个脚本,这就很方便。ScriptScope对象实际上是一个名称空间。要给脚本传入值或从脚本传出值,应把一个变量绑定到ScriptScope上。本例调用SetVariable方法给Python脚本传入prodCount变量和amt变量。它们是totalItems文本框和totalAmt文本框中的值。计算出来的折扣使用GetVariable()方法从脚本中检索。在本例中,reAmt变量包含了我们需要的值。在CalcTax按钮中,调用了Python对象上的方法。CalcTax.py脚本是一个非常简单的方法,它接受一个输入值,加上7.5%的税,再返回新值。代码如下:def CalTax(amount): return amount * 1.075下面是调用CalcTax()的C#代码:这是一个非常简单的过程。这里再次使用与前面相同的配置创建了ScriptRuntime对象。callRate是一个ScriptScope对象,它定义为动态对象,以便轻松地调用CalcTax()方法。这是使用动态类型简化编程工作的一个示例。DynamicObject和ExpandoObject:如果要创建自己的动态对象,可以采用以下两种方法:从DynamicObject中派生,或者使用ExpandoObject。使用DynamicObject需要做的工作较多,因为必须重写几个方法。ExpandoObject是一个可立即使用的密封类。1、 DynamicObject:考虑一个表示人的对象。一般应定义名字、中间吗和姓氏等属性。现在假定要在运行期间构建这个对象,且系统事先不知道该对象有什么属性或该对象可能支持什么方法。此时就可以使用基于DynamicObject的对象。需要这类功能的场合几乎没有,但是目前为止,C#语言还没有提供该功能。先看看DynamicObject:在这个示例中,重写了3个方法TrySetMember()、TryGetMember()和TryInvokeMember()。TrySetMember()方法给对象添加了新方法、属性或字段。本例把成员信息存储在一个Dictionary对象中。传送给TrySetMember()方法的SetMemberBinder对象包含Name属性,它用于标志Dictionary中的元素。TryGetMember()方法根据GetMemberBinder对象的Name属性检索存储在Dictionary中的对象。这些方法如何使用?下面的代码使用了刚才新建的动态对象:看起来很简单,但在哪里调用了重写的方法?正是.NET Framework帮助完成了调用。DynamicObject处理了绑定,我们只需引用FirstName和LastName属性即可,就好像它们一直存在一样。如何添加方法?这很简单。可以使用上例中的WroxDynamicObject,给它添加GetTomorrowDate()方法,该方法接受一个DateTime对象为参数,返回表示第二天的日期字符串。代码如下:这段代码使用Func创建了委托GetTomorrow。该委托表示的方法调用了AddDays,给传入的Date加上一天,返回得到的日期字符串。接着把委托设置为wroxDyn对象上的GetTomorrowDate()方法。最后一行调用新方法,并传递今天的日期。动态功能再次发挥了作用,对象上有了一个有效的方法。2、 ExpandoObject:ExpandoObject的工作方式类似于上一节创建的WroxDynamicObject,区别是不必重写方法,如下面的代码示例所示:注意这段代码与前面的代码几乎完全相同,也添加了FirstName和LastName属性,以及GetTomorrow函数,但它还多做了一件事把一个Person对象集合添加为对象的一个属性。初看起来,这似乎与使用dynamic类型没有区别。但注意dynamic不能仅创建dynamic类型的空对象。这里的使用与dynamic类型有以下区别:因为dynamic类型必须赋予某个对象,所以如果执行GetType调用,它就会报告赋予了dynamic类型的对象类型。所以如果把它赋予了int,GetType就报告它是一个int。它不适用于ExpandoObject或派生自DynamicObject的对象。如果需要控制动态对象中属性的添加和访问,则使该对象派生自DynamicObject是最佳选择。使用DynamicObject,可以重写几个方法,准确地控制对象与运行库的交互方式。而对于其他情况,就应使用dynamic类型或ExpandoObject。并行编程模型中主要包括以下几个重要的部分(多核并行编程):l TPL(Task Parallel Library),提供了两个重要的类Parallel和Task,其中Parallel主要包含了For、Foreach和Invoke重载方法;而Task则实现了对任务执行的异步支持和控制。l PLINQ,LINQ to Object的并行实现。l Data Structures,.NET 4.0包含了一系列支持并行编程的新类型,主要包含并发集合类、同步类型和延迟加载。任务(多核并行编程):.NET 4.0包含新名称空间System.Threading.Tasks,它包含的类抽象出了线程功能。在后台使用ThreadPool。任务表示应完成的某个单元的工作。这个单元的工作可以在单独的线程中运行,也可以以同步方式启动一个任务,这需要等待主调线程。使用任务不仅可以获得一个抽象层,还可以对底层线程进行很多控制。在安排需要完成的工作时,任务提供了非常大的灵活性。例如,可以定义连续的工作在一个任务完成后该执行什么工作。这可以区分任务成功与否。另外,还可以在层次结构中安排任务。例如,父任务可以创建新的子任务。这可以创建一种依赖关系,这样,取消父任务,也会取消子任务。1、 启动任务:要启动任务,可以使用TaskFactory类或Task类的构造函数和Start()方法。Task类的构造函数在创建任务上提供的灵活性较大。在启动任务时,会创建Task类的一个实例,利用Action或Action委托(不带参数或带一个object参数),可以指定应允许的代码。这类似于Thread类。下面定义了一个无参数的方法。在实现代码中,把任务的ID写入控制台中:在上面的代码中,可以看到启动新任务的不同方式。第一种方式使用实例化的TaskFactory类,在其中把TaskMethod()传递给StartNew()方法,就会立即启动任务。第二种方式使用Task类的构造函数。实例化Task对象时,任务不会立即运行,而是指定Created状态。接着调用Task类的Start()方法,来启动任务。使用Task类时,除了调用Start()方法,还可以调用RunSynchronously()方法。这样,任务也会启动,但在调用者的当前线程中它正在运行,调用者需要一直等待到该任务结束。默认情况下,任务是异步运行的。使用Task类的构造函数和TaskFactory类的StartNew()方法时,都可以传递TaskCreationOptions枚举中的值。设置LongRunning选项,可以通知任务调度器,该任务需要较长时间执行,这样调度器更可能使用新线程。如果该任务应关联到父任务上,而父任务取消了,则该任务也应取消,此时应设置AttachToParent选项。PerferFairness的值表示,调度器应提取出已在等待的第一个任务。如果一个任务在另一个任务内部创建,这就不是默认情况。如果任务使用子任务创建了其他工作,子任务就优先于其他任务。它们不会排在线程池队列中的最后。如果这些任务应以公平的方式与所有其他任务一起处理,就设置该选项为PerferFairness。2、 连续的任务:通过任务,可以指定在任务完成后,应开始运行另一个特定任务,例如,一个使用前一个任务的结果的新任务,如果前一个任务失败了,这个任务就应执行一些清理工作。任务处理程序或者不带参数或者带一个对象参数,而连续处理程序有一个Task类型的参数,这里可以访问起始任务的相关信息:连续任务通过在任务上调用ContinueWith()方法来定义。也可以使用TaskFactory类来定义。tl.OnContinueWith(DoOnSecond)方法表示,调用DoOnSecond()方法的新任务应在任务tl结束时立即启动。在一个任务结束时,可以启动多个任务,连续任务也可以有另一个连续任务,如下面的例子所示:无论前一个任务是如何结束的,前面的连续任务总是在前一个任务结束时启动。使用TaskCreationOptions枚举中的值,可以指定,连续的任务只有在起始任务成功(或失败)结束时启动。一些可能的值是OnlyOnFaulted、NotOnFaulted、OnlyOnCanceled、NotOnCanceled和OnlyOnRanToCompletion。3、 任务层次结构:利用任务连续性,可以在一个任务结束后启动另一个任务。任务也可以构成一个层次结构。一个任务启动一个新任务时,就启动了一个父/子层次结构。下面的代码段在父任务内部新建一个任务。创建子任务的代码与创建父任务的代码相同,唯一的区别是这个任务从另一个任务内部创建。如果父任务在子任务之前结束,父任务的状态显示为WaitingForChildrenToComplete。只要子任务也结束时,父任务的状态就变成RanToCompletion。当然,如果父任务用TaskCreationOptions枚举中的DetachedFromParent创建子任务时,这就无效。取消父任务,同样也会取消子任务。4、 任务的结果:任务结束时,它可以把一些有用的状态信息写到共享对象中。这个共享对象必须是线程安全的。另一选项是使用返回某个结果的任务。使用Task类的泛型版本,就可以定义返回某个结果的任务的返回类型。为了返回某个结果任务调用的方法可以声明为带任意返回类型。示例方法TaskWithReult()利用一个元祖(也就是Tuple)返回两个int值。该方法的输入可以使void或object类型,如下所示:定义一个调用TaskWithResult()方法的任务时,要使用泛型类Task。泛型参数定义了返回类型。通过构造函数,把这个方法传递给Func委托,第二个参数定义了输入值。因为这个任务在object参数中需要两个输入值,所以还创建了一个元祖(也就是Tuple)。接着启动该任务。Task实例tl的Result属性被禁用,并一直等到该任务完成。任务完成后,Result属性包含任务的结果。注:可以通过Wait/WaitAny/WaitAll等方式等待task执行完成,分别表示当前任务、任意任务、所有任务完成,而task执行中出现的异常,CLR将异常信息包装在AggregateException中。Parallel (System.Threading.Tasks.Parallel)(多核并行编程):Parallel静态类提供了3个重载的静态方法:For、Foreach还有Invoke,其中For和Foreach通过遍历数据源实现数据并行化,而Invoke是实现隐式任务并行化的基础。Invoke运用实例(任务并行化):public static void DoInvoke()Parallel.Invoke()=Console.WriteLine(“Run task A”),()=Console.WriteLine(“Run task B”),()=Console.WriteLine(“Run task C”);运用Task实现的任务并行化:public static void DoTasks()Task tasks=new TaskTask.Factory.StartNew()=Console.WriteLine(“Run task A”),Task.Factory.StartNew()=Console.WriteLine(“Run task B”),Task.Factory.StartNew()=Console.WriteLine(“Run task C”);Task.WaitAll(tasks);Console.WriteLine(“Tasks Finished.”);For应用实例:执行的结果:从结果中可以看出:几个循环可能共用一个线程。说明:在For()方法中,前两个参数定义了循环的开头和结束。示例从0迭代到9。第3个参数是一个Action委托。整数参数是循环的迭代次数,该参数被传递给Action委托引用的方法。Parallel.For()方法的返回类型是ParallelLoopResult结构,它提供了循环是否结束的信息。 说明:For()方法的一个重载版本接受第3个Action类型的参数。使用这些参数定义一个方法,就可以调用ParallelLoopState的Break()或Stop()方法,以影响循环的结果。说明:Parallel.For()方法可能使用几个线程来执行循环。如果需要对每个线程进行初始化,就可以使用Parallel.For()方法。除了from和to对应的值之外,For()方法的泛型版本还接受3个委托参数。第一个参数的类型是Func,因为这里的例子对于TLocal使用字符串,所以该方法需要定义为Func,即返回string的方法。这个方法仅对于用于执行迭代的每个线程调用一次(几个循环可能共用同一个线程,这个从实际的执行代码中就可以看出,上面给出的第一个For例子的执行结果截图也可以看出这个情况)。第二个委托参数为循环体定义了委托。在示例中,该参数的类型是Func。其中第一个参数是循环迭代,第二个参数ParallelLoopState允许停止循环。循环体方法通过第3个参数接收从init方法返回的值,循环体方法还需要返回一个值,其类型是用泛型for参数定义的。For()方法的最后一个参数指定一个委托Action;在该示例中,接收一个字符串。这个方法仅对于每个线程调用一次,这是一个线程退出方法(多个循环可能共用一个线程)。ForEach()应用实例:说明:以异步方式遍历。说明:如果需要中断循环,就可以使用ForEach()方法的重载版本和ParallelLoopState参数。其方式与前面的For()方法相同。ForEach()方法的一个重载版本也可以用于访问索引器,从而获得迭代次数。PLINQ(多核并行编程):PLINQ(Parallel LINQ)是声明式的数据并行性框架,提供了以最简单的方式实现并行的LINQ查询操作的框架支持,可接受任何的LINQ to object和LINQ to xml查询,而开发者则完全像进行LINQ to object查询一样,将数据查询自动执行在多核。具体来说,以下两行代码的区别仅仅在于通过AsParallel()就自动实现了将查询操作运行于多个线程中,对于习惯LINQ操作的开发者而言,PLINQ让一切看起来没有任何改变,而调动多核运行的复杂逻辑已经被实现了。var ls=list.Select(x=x+10);var ls=list.AsParallel(). Select(x=x+10);类似于AsParallel()的扩展方法被统一封装在System.Linq.ParallelEnumberable类中,主要公开了AsParallel、AsSequential、AsOrdered、ForAll、WithCancellation以及Aggregate等扩展方法。剖析这些公开的方法,基本都是对IEnumberable或ParallelQuery的扩展,因此PLINQ支持所有的LINQ运算符,对于任何内存中的集合例如IList、IEnumerable都可以通过PLINQ来赋予并行查询。PLINQ的使用总结:l 默认情况下,PLINQ最多可开启64个处理器,可以通过WithDegreeOfParallelism指定。l 默认情况下,PLINQ会使用所有的处理器,但是可以通过WithDegreeOfParallelism()方法来指定可使用的处理器数量,例如下面的代码将限制PLINQ最多使用4个处理器。var list=Enumerable.Range(1,1000).AsParallel().WithDegreeOfParallelism(4);l PLINQ并不支持LINQ to SQL,LINQ to SQL的具体查询并不发生在内存,而是发生在数据库。l 在运行时,PLINQ基础架构会分析查询结构,并分析在并行或顺序执行的查询成本,然后选择是否并行执行。PLINQ在高成本并发算法和低成本顺序算法中,以顺序算法为默认选择。当然,可以通过WithExecutionMode手动选择ParallelExecutionModel.ForceParallelism,要求PLINQ强制执行并行算法。l 因为并行执行并不能保证执行的顺序性,因此list.AsParallel().Select(x=x+10)并不按list原本的元素顺序输出,如果必须保持查询之前的顺序性,可以应用AsOrder()方法,但是保持顺序将引入额外的开销,对执行性能造成影响。l 并行并不意味着绝对的性能提升,并且并行本身也会增加额外的开销。在数据源数据流较小时,应用顺序LINQ(LINQ to Object)在性能上要优于PLINQ,因为并行本身的花销是可观的,并行并不意味着性能的绝对提升,必须量力而行。l 确保并行执行的循环逻辑是线程安全的,该逻辑将可能运行于多线程中。l 尽量避免在lock中执行任务等待,因为任务的执行逻辑可能锁定了同一个变量,并导致死锁的发生。l 并行计算,让摩尔定理重新焕发光彩,信息技术仍然以大刀阔斧的步子在高速发展。l 对并行投入更多的考量,是未来开发的重要方面。命名参数和可选参数(例子说明):既有命名参数又有可选参数的方法:static void Write(string fileName,string method=”add”,int x=1,int y=2)调用上面的方法:Write(“C:test.txt”);Write(“C:test.txt”,”sub”);Write(“C:test.txt”,y:200,x:100);Write(“C:test.txt”,”sub”,100,200);Write(“C:test.txt”x:200,y:100,method:”sub”);关于上面例子的说明:1) 命名参数和可选参数一定程度上实现了重载方法的功能,对于程序的简化开发有很大的帮助。2) 命名参数是方法使用时必须带的参数。3) 可以依次给命名参数和可选参数全部赋值来调用方法。4) 如果想要对可选参数不按照原本的顺序赋值的话,需要加上“参数名:+具体的值”。命名参数和可选参数的应用场合举例:可选参数和命名参数只是简单的语法糖游戏,不过在某些时候可以给代码带来很完美的解决方案,以ASP.NET MVC为例,很多时候需要考虑服务端分页的情况,因此page和count这两个参数就会广泛存在于很多Action(也就是方法)中,例如:public ActionResult Posts(string author,int page=0,int count=20)/省略return View();因此,可以很灵活地通过默认的分页信息page和count实现对服务端的数据分页请求:/blog/posts/blog/posts?page=2/blog/posts?count=15/blog/posts?page=2&count=10以上请求在可选参数的帮助下都是合法的。在不指定page和count分页信息时,服务端将自动以可选参数的默认值作为分页信息提交,完善了对于参数的简洁度和兼容性。可选参数也广泛应用在与COM对象的交互中,很多Office函数包括了十多个参数,可选参数极大地简化了这种调用。协变与逆变:1、 协变首先,自定义支持协变的泛型接口: public interface IFactory T Create(); public class Factory:IFactorypublic T Create()return (T)Activator.CreateInstance();支持协变的IFactory接口,以关键字out标志类型参数T,那么对于应用端,下面的调用过程就实现了对T的协变(下面的类Derived继承自Base类):static void Main(string args)IFactory f=new Factory();IFactory f2=f; /协变过程Base b=f2.Create();b.Write();对于协变,必须遵循以下规则:l 泛型参数以out关键字标志,并且只能应用于只读属性、方法或者委托的返回值。l 目标泛型类型的类型参数(例如Base)必须是被转换泛型类型的类型参数(例如Derived)的基类。l 在.NET中有代表性的实现了协变的泛型接口有IEnumerable、IQuerable、IEnumerator、IGrouping等。2、 逆变首先,自定义支持逆变的泛型接口:public interface INotifier where TNotification : INotificationvoid Notify(TNotification notification);public class Notifier : INotifier where TNotification : INotificationpublic void Notify(TNotification notification)Console.WriteLine(notification.Message);其中的INotification实体及其子类定义如下:public interface INotificationstring Message get; public abstract class Notification : INotificationpublic abstract string Message get; public class MailNotification : Notificationpublic override string Messageget return “You got a email.”; 支持逆变的INotifier接口,以关键字in标志类型参数TNotification,那么对于应用端,下面的调用过程就实现了对TNotification的逆变:static void Main(string args)INotifier notifier=new Notifier();INotifier mainNotifier=notifier; /逆变过程mailNotifier.Notify(new MailNotification();对于逆变,必须遵循以下规则:l 泛型参数以in关键字标识,并且只能应用于只写属性、方法或委托的参数。l 目标泛型类型的类型参数(例如MailNotification)必须是被转换泛型类型的类型参数(例如INotification)的子类。l 在.NET中有代表性的实现了逆变的泛型接口有IComparer、IComparable等。关于协变与逆变,有如下的总结:l 在.NET新特性中,协变与逆变只对委托和接口有效,对于普通的泛型类或者泛型方法无效。l 协变与逆变的可变类型参数必须是引用类型,不能是值类型。从继承的角度而言,值类型都对应于封闭的结构体,是密封的且不具有继承性,所以类型转换存在不兼容问题。l 泛型接口或者泛型委托可以同时具有协变和逆变类型参数,例如典型的Func类型。例如:public delegate TResult Func(T arg);l 在.NET 2.0/3.0时代,委托已经支持一定程度的协变和逆变,不过只是简单的基类与派生类实例化对象的之间转换,在2.0/3.0不支持基类与派生类的泛型对象之间的转换。l 在.NET 1.0时代,协变性体现在数值类型上。l 一般的类或者结构体并不具备可变性,必须明确可变性的应用规则与场合。Lazy的应用:Lazy其定义如下:Serializablepublic class Lazypublic Lazy();public Lazy(bool isThreadSafe);public Lazy(Func valueFactory);public Lazy(LazyThreadSafetyMode mode);public Lazy(Func valueFactory,bool isThreadSafe);public Lazy(Func valueFactory,LazyThreadSafetyMode mode);public bool IsValueCreated get; public T Value get; public override string ToString();假设,我们有一个大的类:public class Bigpublic int ID get; set; /other resourcesstatic void Main(string args)/在创建延迟对象时并未实例化Lazy

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论