C# 委托进阶
本文参考自:https://wenku.baidu.com/view/41ab91d3c1c708a1284a44d7.html?qq-pf-to=pcqq.c2c
1、为什么委托定义的返回值通常为void?
尽管并非必须,但是大多数情况委托定义的返回值都为void,因为这部分委托基本都是需要绑定多个方法,也就是当前委托允许多个订阅者注册,但是当主函数执行委托对象上注册的方法时,不会返回结果,只会返回最后一个方法的结果值,这一点可以通过调试下面的代码就可以看出,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Delegate
{
public delegate int? OpearteEventHandler(int? a,int? b);
class Program
{
static void Main(string[] args)
{
Program p = new Program();
int? a = ;
int? b = ;
OpearteEventHandler ah = p.Add;
ah += p.Sub;
ah += p.Multiply;
p.ShowResult(a, b, ah);
}
public void ShowResult(int? a, int? b, OpearteEventHandler handler)
{
string result = "";
result += handler(a, b).ToString();
Console.WriteLine(result);
Console.ReadKey();
}
public int? Add(int? a, int? b)
{
return a + b;
}
public int? Sub(int? a, int? b)
{
return a - b;
}
public int? Multiply(int? a, int? b)
{
return a * b;
} }
}
对上面的代码进行调试发现,Add方法和Sub方法的结果值并没有被返回,只返回了最后Multiply的值,除了上面这个原因外,发布者和订阅者的关系是松耦合的,发布者根本不关心谁订阅了它的事件,为什么要订阅,跟别说返回值了,发布者要做的就是执行订阅它事件的方法,所以当委托绑定了多个事件时,返回值常常是void的原因.
2、如何让事件只允许一个客户订阅
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Delegate
{
public delegate string GeneralEventHandler();
class Program
{
static void Main(string[] args)
{
Publisher pr = new Publisher();
pr.Register(new Program().ReturnValue);
pr.DoSomething();
}
public string ReturnValue()
{
return "返回值";
}
public string ReturnValue1()
{
return "返回值1";
}
public class Publisher
{
private event GeneralEventHandler NumberChanged;//声明一个事件
public void Register(GeneralEventHandler handler)
{
NumberChanged += handler;
}
public void UnRegister(GeneralEventHandler handler)
{
NumberChanged -= handler;
}
public void DoSomething()
{
if (NumberChanged != null)
{
string str = NumberChanged();
Console.WriteLine("return value is {0}", str);
}
}
}
}
}
注意:
(1)、在UnRegister()中,没有进行任何判断就使用了NumberChanged -= method 语句。这是因为即使method 方法没有进行过注册,此行语句也不会有任何问题,不会抛出异常,仅仅是不会产生任何效果而已。
(2)、NumberChanged被声明为私有的,所以客户端无法看到它,所以无法通过它来触发事件,调用订阅者的方法,而只能通过Register()和UnRegister()方法来注册和取消注册
但是上面的代码并不是最好的实现,C#提供事件访问器,也可以实现上面的功能
3、事件访问器
C#提供事件访问器,通过它可以将委托封装成一个变量,像访问类中的属性那样,来访问事件,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Delegate
{
public delegate string GeneralEventHandler();
class Program
{
static void Main(string[] args)
{
Publisher pr = new Publisher();
pr.NumberChanged += new Subscriber().ReturnValue;
pr.DoSomethings();
}
public class Publisher
{
private GeneralEventHandler numberChanged;//声明一个委托变量
// 事件访问器的定义
public event GeneralEventHandler NumberChanged
{
add
{
numberChanged = value;
}
remove
{
numberChanged -= value;
}
}
public void DoSomethings()
{
if (numberChanged != null)
{
string str = numberChanged();
Console.WriteLine("return value is {0}", str);
}
}
}
public class Subscriber
{
public string ReturnValue()
{
return "返回值1";
}
}
}
}
上面代码中的类似属性的public event GeneralEventHandler NumberChanged{ add{....}remove{....} }就是事件访问器了,使用了事件访问器之后,DoSomethings就只能通过numberChanged委托变量来触发事件了,而不能使用NumberChanged访问器来触发,因为它只用于注册和取消注册事件。
4、获得多个返回值与异常处理
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Delegates
{
public delegate string GeneralEventHandler(int num);
class Program
{
static void Main(string[] args)
{
Publisher pr = new Publisher();
pr.NumberChanged +=new GeneralEventHandler(new Subscriber().ReturnValue);
pr.NumberChanged += new GeneralEventHandler(new Subscriber1().ReturnValue);
List<string> list=pr.DoSomething();
Console.WriteLine(list[]+"..."+list[]);
}
public class Publisher
{
public event GeneralEventHandler NumberChanged;//定义一个事件
public List<string> DoSomething()
{
List<string> list = new List<string>();
if (NumberChanged == null) return list;
//获得委托数组
Delegate[] delArr = NumberChanged.GetInvocationList();
foreach (Delegate del in delArr) {
GeneralEventHandler handler = (GeneralEventHandler)del;//拆箱
list.Add(handler());
}
return list;
} } public class Subscriber
{
public string ReturnValue(int num)
{
Console.WriteLine("Subscriber1 invoked, number:{0}", num);
return "[Subscriber returned]";
}
}
public class Subscriber1
{
public string ReturnValue(int num)
{
Console.WriteLine("Subscriber1 invoked, number:{0}", num);
return "[Subscriber1 returned]";
}
}
}
}
上面的方法获得了两个订阅者的返回值,但是前面说过很多情况下,委托的定义都不包含返回值,所以上面的方法介绍的似乎没什么实际意义。但是其实上面这种方法来触发事件的情况应该是在异常处理中,因为很有可能在触发事件时,订阅者的方法抛出异常,这一异常可能会引起发布者的异常,使得发布者的程序停止,而后面的订阅者的方法将不会被执行,所以我们需要加上异常处理。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Delegates
{
public delegate void GeneralEventHandler(object sender, EventArgs e);
class Program
{
static void Main(string[] args)
{
Publisher pr = new Publisher();
pr.NumberChanged += new GeneralEventHandler(new Subscriber().OnEvent);
pr.NumberChanged += new GeneralEventHandler(new Subscriber1().OnEvent);
pr.NumberChanged += new GeneralEventHandler(new Subscriber2().OnEvent);
pr.DoSomethings(); }
public class Publisher
{
public event GeneralEventHandler NumberChanged;//定义一个事件
public void DoSomethings() {
Program.TraverseEvent(NumberChanged, this, EventArgs.Empty);
} } public class Subscriber
{
public void OnEvent(object sender, EventArgs e) { Console.WriteLine("Subscriber Invoked!"); }
}
public class Subscriber1
{
public void OnEvent(object sender, EventArgs e) { throw new Exception("Subscriber1 Failed"); }
}
public class Subscriber2
{
public void OnEvent(object sender, EventArgs e) { Console.WriteLine("Subscriber2 Invoked!"); }
} /// <summary>
/// 遍历所有的订阅事件,获得多个返回值以及异常处理
/// </summary>
/// <param name="del">绑定完方法的委托</param>
/// <param name="args">传递给订阅方法的参数</param>
/// <returns></returns>
public static object[] TraverseEvent(Delegate del, params object[] args)
{
List<object> list = new List<object>();
if (del != null)
{
Delegate[] delArr = del.GetInvocationList();//获得委托链表
foreach (Delegate method in delArr)
{
try
{
object obj = method.DynamicInvoke(args);//执行订阅方法,并传递参数,获得其返回值
if (obj != null)
{
list.Add(obj);
}
}
catch{
}
}
}
return list.ToArray();
}
}
}
DynamicInvoke方法是调用委托最通用的方法了,适用于所有类型的委托。它接受的参数为object[],也就是说它可以将任意数量的任意类型作为参数,并返回单个object 对象。
ok,通过结果发现Subscriber1().OnEvent订阅方法抛出的异常并没有影响Subscriber2().OnEvent的方法的执行。当然因为
catch什么都没有做!
5、订阅者方法超时的处理
订阅者除了可以通过异常的方式影响发布者外,还可以通过另外一种方式影响发布者:超时,一般说超时指的是方法的执行超过了某个时间,而这里的含义是,方法的执行的时间比较长,2s、3s、5s都算做超时,是一个很模糊的概念。而超时和异常的区别就在于,超时并不会影响事件的正确触发和正常的运行,却会导致事件触发后需要很长时间才会结束,在依次执行订阅者方法的这段时间内,客户端程序会被中断,什么也不能做。应为当执行订阅者中的方法时(通过委托相当于依次调用了所有注册了的方法),当前线程会转到订阅者的方法中,调用订阅者方法的客户端则会被中断,只有当方法执行完毕并返回时,控制权才会重新回到调用订阅者方法的客户端的客户端中。如果你调试过上面案例的代码的话,我相信这个特点不难发现。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace Delegates
{ public class Program
{ static void Main(string[] args)
{
Publisher pr = new Publisher();
pr.MyEvent += new EventHandler(new Subscriber().OnEvent);
pr.MyEvent += new EventHandler(new Subscriber1().OnEvent);
pr.MyEvent += new EventHandler(new Subscriber2().OnEvent);
pr.DoSomethings();
} public static object[] traverseEvent(Delegate del,params object[] args) {
List<object> list = new List<object>();
if (del != null) {
Delegate[] dels=del.GetInvocationList();
foreach (Delegate method in dels) {
try
{
//执行传入委托的订阅方法,传入参数,并且获得其返回值,而且如果传入委托中间部分的订阅事件发生异常,不会影响后面事件的执行
object obj = method.DynamicInvoke(args);
if (obj != null)
{
list.Add(obj);
}
}
catch{ }
}
}
return list.ToArray();
}
}
public class Publisher
{
public event EventHandler MyEvent;
public void DoSomethings() {
Console.WriteLine("DoSomethings invoked!");
Program.traverseEvent(MyEvent, this, EventArgs.Empty);
}
} public class Subscriber
{
public void OnEvent(object sender,EventArgs args) {
throw new Exception("Subscriber Failed");
}
} public class Subscriber1
{
public void OnEvent(object sender, EventArgs args)
{
Console.WriteLine("Subscriber1 begin execute,time is {0}", DateTime.Now.ToLongTimeString());
Thread.Sleep();
Console.WriteLine("Wait for 3 seconds,Subscriber1 executed end, time is {0}", DateTime.Now.ToLongTimeString());
}
}
public class Subscriber2
{
public void OnEvent(object sender, EventArgs args)
{
Console.WriteLine("Subscriber2 begin execute,time is {0}",DateTime.Now.ToLongTimeString());
Console.ReadKey();
}
}
}
通过方法的执行事件可以发现,Subscriber2方法是在Subscriber1的方法等待3秒之后才执行的,但是在前面说过,很多情况下,尤其是在远程调用的时候(比如所在Remoting中),发布者和订阅者应该是完全的松耦合的,发布者不关心谁订阅了它,为什么要订阅它,订阅它的方法有什么返回值,不关心订阅者方法会不会抛出异常,当然也不关心订阅者方法需要多少时间才能执行完毕.它只要在事件的发生的一刹那告诉订阅者事件已经发生,并将相关参数传递给订阅者事件。而订阅者方法不管是执行失败还是超时都不应该影响发布者,而上面的例子发布者必须等待Subscriber1中的发发执行完毕才会执行Subscriber2中的方法,所以需要解决这个问题。
我们都知道委托实际上是一种数据结构,当每定义一个委托,实际上这个委托实例都会继承自MulticastDelegate这个完整的类,而MulticastDelegate这个类则会继承Delegate数据结构,而MulticastDelegate类中包含Invoke()和BeginInvoke()和EndInvoke()等方法,所以间接的每个委托的实例也可以调用这些方法。下面是一个委托被调用的过程:
(1)、调用Invoke方法,中断发布者客户端的操作
(2)、开启一个线程
(3)、通过线程去执行所有订阅者的方法
(4)、所有订阅者方法执行完毕,将控制权返还给发布者客户端
注意:Invoke()方法是同步执行的,也就是说如果某一个订阅方法超时了,那么其下面的方法会等到它执行完毕之后,在执行
ok,介绍完Invoke之后,想必上面的超时问题为什么会发生,应该一目了然了,结下了开始讲解决方法,BeginInvoke()和EndInvoke()方法,在.NET中异步执行的方法通常会成对出现,并且以Begin和End作为方法的开头(如Stream 类的BeginRead()和EndRead()方法了),他们用于方法的异步执行.
(1)、BeginInvoke()方法简介:即在发布者客户端吊用委托之后,当前委托实例调用BeginInvoke()方法,该方法是异步执行,它会从线程池中抓取一个闲置线程,交由这个线程去执行订阅者中的方法,而客户端线程则继续执行接下来的代码,通过这种多线程的方式,达到了异步的效果,也避免了上面单线程阻塞的问题。
(2)、BeginInvoke()方法接受"动态"的参数个数和类型,具体的参数个数是根据调用BeginInvoke方法的委托所决定的,代码如下:
public delegate void EventHandler1(string a,int b);
eh.BeginInvoke("a", , null, null);
这里的代码可能不合理,但只是举例说明,这里调用BeginInvoke()方法的是EventHandler,EventHandler委托接受两个参数string和int,所以BeginInvoke前两个参数也是string和int,这个是编译时,根据委托的定义动态生成的.
(3)、BeginInvoke()方法接受"动态"的参数个数和类型,但最后两个参数是确定的,一个是AsyncCallback(回调函数),另一个是object
(4)、当在委托上调用BeginInvoke方法时,当委托对象只能包含一个方法,对于有多个订阅者注册的情况,只能通过GetInvocationList()获取委托链表,遍历它们,分别操作
(5)、如果订阅者方法抛出异常,.NET会捕捉到它,但是只有在调用EndInvoke()方法时,才会将异常抛出,在本例中,因为我们不关心订阅者的情况,所以无需处理异常,因为即使异常抛出,也是在执行订阅者方法的线程上,所以不会影响到发布者客户端,客户端甚至不知道订阅者发生了异常,这有时是好事有时是坏事.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace Delegates
{
public delegate void GeneralEventHandler(object sender, EventArgs e);
class Program
{
static void Main(string[] args)
{ Publisher pr = new Publisher();
pr.NumberChanged += new EventHandler(new Subscriber().OnEvent);
pr.NumberChanged += new EventHandler(new Subscriber1().OnEvent);
pr.NumberChanged += new EventHandler(new Subscriber2().OnEvent);
pr.DoSomethings(); }
public class Publisher
{
public event EventHandler NumberChanged;//定义一个事件
public void DoSomethings() {
if (NumberChanged != null) {
Delegate[] delArr = NumberChanged.GetInvocationList();//获得委托链表
foreach (Delegate method in delArr) {
EventHandler handler = (EventHandler)method;
handler.BeginInvoke(this, EventArgs.Empty, null, null);
}
}
} } public class Subscriber
{
public void OnEvent(object sender, EventArgs e) {
Thread.Sleep(TimeSpan.FromSeconds());//模拟休息3秒
Console.WriteLine("Wait for 3 seconds,Subscriber Invoked!");
}
}
public class Subscriber1
{
public void OnEvent(object sender, EventArgs e) {
throw new Exception("Subscriber1 Failed"); //模拟抛出异常
}
}
public class Subscriber2
{
public void OnEvent(object sender, EventArgs e) { Console.WriteLine("Subscriber2 Invoked!"); }
} }
}
ok,通过结果发现,Subscriber2的方法最先执行,并没有等待Subscriber的方法执行完毕,而且Subscriber1的异常也没有抛出,发布者客户端并没有因为这个异常而停止操作。
6、委托和方法的异步调用
通常情况下,如果需要异步执行一个耗时的操作,我们会新开一个线程,然后让这个线程去执行代码。但是对于每一个异步调用都用线程去操作显然会对性能造成影响,同时操作也相对繁琐一些,.NET中可以通过委托进行方法的异步调用,就是说客户端在异步调用方法时,本身并不会因为方法的调用而终止,而是从线程中抓取一个线程去执行该方法,主线程继续执行自己的代码,这样就实现了代码的并行执行,使用线程池的好处就是避免了频繁的进行异步调用时,创建、销毁线程的开销。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace Delegates
{
public delegate int AddEventHandler(int a,int b);
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Client Application Started!The time is {0}",DateTime.Now);
Thread.CurrentThread.Name = "主线程";
Calculator cal = new Calculator();
AddEventHandler handler = new AddEventHandler(cal.Add);
IAsyncResult asyncResult = handler.BeginInvoke(, , null, null);
for (int i = ; i <= ; i++)
{
Thread.Sleep(TimeSpan.FromSeconds());
Console.WriteLine("{0}: 主线程休息了 {1} 秒.", Thread.CurrentThread.Name, );
}
int res = handler.EndInvoke(asyncResult);
Console.WriteLine("异步调用Add方法的结果: {0}\n", res);
Console.WriteLine("Client Application ended!The time is {0}", DateTime.Now);
Console.WriteLine("按任意键继续..."); Console.ReadLine();
}
}
}
public class Calculator
{
public int Add(int a, int b)
{
if (Thread.CurrentThread.IsThreadPoolThread)
{
Thread.CurrentThread.Name = "线程池中的线程";
}
Console.WriteLine("-----------------------------------------------");
Console.WriteLine("Add方法开始执行!");
for (int i = ; i <= ; i++)
{
Thread.Sleep(TimeSpan.FromSeconds());
Console.WriteLine("{0}: 休息了 {1} 秒.", Thread.CurrentThread.Name, );
}
Console.WriteLine("Add方法执行完毕!");
Console.WriteLine("-----------------------------------------------");
return a + b;
} }
从输出可以看出,整个应用程序执行了3秒种时间,但是主线程和子线程一共休息了6秒,所以可以推断出,主线程和子线程是并行的,不是串行的EndInvoke方法获得了返回值.
接下来说BeginInvoke方法的另外两个参数,一个是AsyncCallback是一个委托类型,它用于方法的回调,也就是当异步方法调用完毕时,自动调用的方法,它的定义为:
public delegate void AsyncCallback(IAsyncResult ar);
第二个参数是Object类型用于传递任何你想要的数据,它可以通过IAsyncResult的AsyncState属性获得
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace Delegates
{
public delegate int AddEventHandler(int a,int b);
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Client Application Started!The time is {0}",DateTime.Now);
Thread.CurrentThread.Name = "主线程";
Calculator cal = new Calculator();
AddEventHandler handler = new AddEventHandler(cal.Add);
AsyncCallback callBack = new AsyncCallback(OnAddComplete);
int data = ;
IAsyncResult asyncResult = handler.BeginInvoke(, , callBack, data);
for (int i = ; i <= ; i++)
{
Thread.Sleep(TimeSpan.FromSeconds());
Console.WriteLine("{0}: 主线程休息了 {1} 秒.", Thread.CurrentThread.Name, );
}
Console.WriteLine("Client Application ended!The time is {0}", DateTime.Now);
Console.WriteLine("按任意键继续..."); Console.ReadLine();
}
static void OnAddComplete(IAsyncResult asyncResult)
{
AsyncResult result = (AsyncResult)asyncResult;
AddEventHandler del = (AddEventHandler)result.AsyncDelegate;
int data = (int)asyncResult.AsyncState;
int rtn = del.EndInvoke(asyncResult);
Console.WriteLine("{0}: Result, {1}; Data: {2}\n", Thread.CurrentThread.Name, rtn, data);
}
}
}
public class Calculator
{
public int Add(int a, int b)
{
if (Thread.CurrentThread.IsThreadPoolThread)
{
Thread.CurrentThread.Name = "线程池中的线程";
}
Console.WriteLine("-----------------------------------------------");
Console.WriteLine("Add方法开始执行!");
for (int i = ; i <= ; i++)
{
Thread.Sleep(TimeSpan.FromSeconds());
Console.WriteLine("{0}: 休息了 {1} 秒.", Thread.CurrentThread.Name, );
}
Console.WriteLine("Add方法执行完毕!");
Console.WriteLine("-----------------------------------------------");
return a + b;
} }
ok,异步方法执行完毕之后,立即调用了OnAddComplete方法,并且data数据成功传递了过去;
注意:
(1)、在调用EndInvoke方法时可能会抛出异常,所以需要加到try{}catch{}块中
(2)、执行回调方法的线程并不是Main Thread,而是Pool Thread
(3)、我们在调用BeginInvoke()后不再需要保存IAysncResult 了,因为AysncCallback 委托将该对象定义在了回调方法的参数列表中
(4)、通过BeginInvoke()最后一个Object参数,可以给回调函数传参
C# 委托进阶的更多相关文章
- .Net之美读书系列(二):委托进阶
这次看书的知识点: 事件访问器 如果一个委托中注册了多个事件且需要获取其返回值的方法 委托的异常处理 委托处理超时的方法 异步委托 事件访问器 职能有: 1.对委托属性进行封装,不再直接该委托变量直接 ...
- C#中委托和事件
目 录 将方法作为方法的参数 将方法绑定到委托 更好的封装性 限制类型能力 范例说明 Observer 设计模式简介 实现范例的Observer 设计模式 .NET 框架中的委托与事件 为什么委托定义 ...
- ORM查询语言(OQL)简介--高级篇(续):庐山真貌
相关文章内容索引: ORM查询语言(OQL)简介--概念篇 ORM查询语言(OQL)简介--实例篇 ORM查询语言(OQL)简介--高级篇:脱胎换骨 ORM查询语言(OQL)简介--高级篇(续):庐山 ...
- 第3章 C#中的委托和事件
.NET框架中的委托和事件 using System; using System.Collections.Generic; using System.Linq; using System.Text; ...
- ORM查询语言OQL
ORM查询语言(OQL)简介--高级篇(续):庐山真貌 相关文章内容索引: ORM查询语言(OQL)简介--概念篇 ORM查询语言(OQL)简介--实例篇 ORM查询语言(OQL)简介--高级篇:脱胎 ...
- 【转载】C# 中的委托和事件(详解:简单易懂的讲解)
本文转载自http://www.cnblogs.com/SkySoot/archive/2012/04/05/2433639.html C# 中的委托和事件(详解) C# 中的委托和事件 委托和事件在 ...
- C# 中的委托和事件(详解)
C# 中的委托和事件 委托和事件在 .NET Framework 中的应用非常广泛,然而,较好地理解委托和事件对很多接触 C# 时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太 ...
- 【转载】C# 中的委托和事件(详解)
<div class="postbody"> <div id="cnblogs_post_body" class="blogpost ...
- [.net 面向对象程序设计进阶] (5) Lamda表达式(一) 创建委托
[.net 面向对象程序设计进阶] (5) Lamda表达式(一) 创建委托 本节导读: 通过学习Lambda表达式,学会创建委托和表达式目录树,深入了解Lambda的特性,让你的代码变的更加清晰. ...
随机推荐
- Java程序中做字符串拼接时可以使用的MessageFormat.format
Java里从来少不了字符串拼接的活,Java程序员也肯定用到过StringBuffer,StringBuilder,以及被编译器优化掉的+=.但这些都和下文要谈的无关. 比如有这样的字符串: 张三将去 ...
- Android-Activity启动模式(launchMode)
Activity启动模式是非常重要的一块内容,启动模式直接关系到用户的体验 和 性能的提升等 Activity启动模式分为四种: 如果不配置:launchMode,默认就是:standard 标准的 ...
- Java Web系列:Spring Security 基础
Spring Security虽然比JAAS进步很大,但还是先天不足,达不到ASP.NET中的认证和授权的方便快捷.这里演示登录.注销.记住我的常规功能,认证上自定义提供程序避免对数据库的依赖,授权上 ...
- vs2015上使用github进行版本控制
我是用的是vs2015企业版 一.首先创建项目,右下角选择新建git存储库 二.在工具栏选择团队-管理连接,打开团队资源管理器,点击同步 . 三.选择下面的发布选项 四.在gitgub上新建仓库,得到 ...
- Asp.NetCore取配置信息
本文简单介绍Asp.NetCore取自定义配置信息的方法,要取配置信息首先得有配置文件. 1, 本例新建了一个TimeOut.json配置文件,和其对应的一个TimeOut类 2, 首先在Startu ...
- WP8.1StoreApp(WP8.1RT)---SystemTray的变化
原Microsoft.Phone.Shell中的SystemTray,已经改到Windows.UI.ViewManagement中StatusBar了. 只能在代码中设置相关属性. 如: 1 2 3 ...
- [SSH]struts2-spring-plugin.jar了解
在struts2-spring-plugin.jar中有一个struts-plugin.xml,里面声明了action类由spring工厂创建.在struts2插件文档里,这样写着“The Sprin ...
- Android ScrollView 去掉 scrollbar 和 阴影
1. 在 layout 里: android:scrollbars="none" android:overScrollMode="never" 2. 代码里 / ...
- 深入了解java虚拟机(JVM) 第九章 class文件结构及意义
Class文件是访问jvm的重要指令,学习了class文件能够更加深入的了解jvm的工作过程.本文只进行概况总结,要进行更加详细的学习class文件,可以参考:https://blog.csdn.ne ...
- 打开SQL Server2000企业管理器时候提示“MMC 无法创建管理单元 ”
今天上午在打开SQL Server 2000 企业管理器时候提示“MMC 无法创建管理单元”错误.