C#中的动态特性
众所周知,C#和Java一样,都是一门静态语言。在C# 4.0之前,想要和动态语言(诸如Python、Javascript等)进行方便地互操作是一件不太容易的事情。而C# 4.0为我们带来的dynamic关键字,使得我们可以方便的和动态语言进行互操作。本文将从如下几个方便来阐述:
1.dynamic的使用
关于dynamic的使用,这里主要讲两个例子:
例子1:
static void Main(string[] args)
{
	int i = 2;
	dynamic j = i;
	Console.WriteLine(j.GetType());//System.Int32
	int s = j + "3";//occur runtime exception
	Console.WriteLine(s);
	Console.ReadKey();
}
正常来说,int s = ? + "3";这句编译是通不过的,因为一个string类型无法隐式的转换成int类型。然而当?为dynamic类型时,这段代码是可以编译通过的,但在运行时会报无法将类型“string”隐式转换为“int”的异常。这看起来似乎是将编译时的错误推迟到了运行时。
例子2:
static void Main(string[] args)
{
	var d = new {i = 1, j = 2};
	Console.WriteLine(Calculate(d));//3
	Console.ReadKey();
}
static dynamic Calculate(dynamic d)
{
	return d.i + d.j;
}
首先声明了一个匿名类型对象,然后将该对象作为参数传给Calculate方法。Calculate方法接受一个dynamic类型的参数,作加操作。这样达到了操作一个隐式类型的效果。
2. dynamic原理(DLR)
在上面的例子中,我们简单感受了下dynamic关键字的强大。一个强大事物的背后总会有各种支撑其发展的事物,dynamic也不例外,[DLR](http://dlr.codeplex.com/)(Dyanmic Language Runtime)库就是其支撑。


以上是DLR的框架结构图。下面我们将通过一个简单的例子,来阐述dynamic的原理:
static void Main(string[] args)
    {
        // Source
        var student = new Student { ID = 1, Name = "jello" };
        // Dynamic Assign
        dynamic d = student;
        Console.WriteLine(d.ID);
        Console.ReadKey();
    }
}
class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
}
通过反编译,代码如下:
private static void Main(string[] args)
	{
		Student student = new Student
		{
			ID = 1,
			Name = "jello"
		};
		object d = student;
		if (Program.<Main>o__SiteContainer1.<>p__Site2 == null)
		{
			Program.<Main>o__SiteContainer1.<>p__Site2 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "WriteLine", null, typeof(Program), new CSharpArgumentInfo[]
			{
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null),
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
			}));
		}
		Action<CallSite, Type, object> arg_D1_0 = Program.<Main>o__SiteContainer1.<>p__Site2.Target;
		CallSite arg_D1_1 = Program.<Main>o__SiteContainer1.<>p__Site2;
		Type arg_D1_2 = typeof(Console);
		if (Program.<Main>o__SiteContainer1.<>p__Site3 == null)
		{
			Program.<Main>o__SiteContainer1.<>p__Site3 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "ID", typeof(Program), new CSharpArgumentInfo[]
			{
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
			}));
		}
		arg_D1_0(arg_D1_1, arg_D1_2, Program.<Main>o__SiteContainer1.<>p__Site3.Target(Program.<Main>o__SiteContainer1.<>p__Site3, d));
		if (Program.<Main>o__SiteContainer1.<>p__Site4 == null)
		{
			Program.<Main>o__SiteContainer1.<>p__Site4 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "WriteLine", null, typeof(Program), new CSharpArgumentInfo[]
			{
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null),
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
			}));
		}
		Action<CallSite, Type, object> arg_189_0 = Program.<Main>o__SiteContainer1.<>p__Site4.Target;
		CallSite arg_189_1 = Program.<Main>o__SiteContainer1.<>p__Site4;
		Type arg_189_2 = typeof(Console);
		if (Program.<Main>o__SiteContainer1.<>p__Site5 == null)
		{
			Program.<Main>o__SiteContainer1.<>p__Site5 = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, "Name", typeof(Program), new CSharpArgumentInfo[]
			{
				CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
			}));
		}
		arg_189_0(arg_189_1, arg_189_2, Program.<Main>o__SiteContainer1.<>p__Site5.Target(Program.<Main>o__SiteContainer1.<>p__Site5, d));
		Console.ReadKey();
	}
	[CompilerGenerated]
	private static class <Main>o__SiteContainer1
	{
		public static CallSite<Action<CallSite, Type, object>> <>p__Site2;
		public static CallSite<Func<CallSite, object, object>> <>p__Site3;
		public static CallSite<Action<CallSite, Type, object>> <>p__Site4;
		public static CallSite<Func<CallSite, object, object>> <>p__Site5;
	}
我们看到,编译器会为我们生成一个o__SiteContainer1类,里面包含四个CallSite:
- <>p__Site2:对应于第一个Console.WriteLine(dynamic)
- <>p__Site3:对应于dynamic.ID
- <>p__Site4:对应于第二个Console.WriteLine(dynamic)
- <>p__Site5:对应于dynamic.Name
大概的步骤如下:
- 将dynamic声明的变量变为object类型
- 解析表达式并执行,通过Binder构造CallSite,内部通过构造Expression Tree实现。Expression Tree可编译成IL,然后交由CLR编译成Native Code
DLR采用三级缓存,包括L0、L1和L2。缓存以不同的方式将信息存储在不同的作用域中。每个调用点包含自己的L0和L1缓存。而L2缓存可以被多个类似的调用点共享。拿上面例子为例:
- 首次构造<>p__Site2时,会通过CallSite<Action<CallSite, Type, object>>.Create(CallSiteBinder)构造CallSite的同时,在L0缓存基于Site History的专用委托,可通过CallSite.Target获取。
- 当调用CallSite.Target委托时,会去调用UpdateDelegates的UpdateAndExecute×××(CallSite)方法,通过该方法去更新缓存并执行委托。
- L1缓存缓存了dynamic site的历史记录(规则),是一个委托数组,L2缓存缓存了由同一个binder产生的所有规则,是一个字典,Key为委托,Value为RuleCache,T为委托类型。
可以做如下实验:
Console.WriteLine("-------Test--------");
        Console.WriteLine("callsite1-Target:" + action.GetHashCode());
        Console.WriteLine("callsite3-Target:" + action1.GetHashCode());
        var rules1 = CallSiteContainer.CallSite1.GetType()
            .GetField("Rules", BindingFlags.Instance | BindingFlags.NonPublic)
            .GetValue(CallSiteContainer.CallSite1) as Action<CallSite, Type, object>[];
        var rules2 = CallSiteContainer.CallSite3.GetType()
            .GetField("Rules", BindingFlags.Instance | BindingFlags.NonPublic)
            .GetValue(CallSiteContainer.CallSite3) as Action<CallSite, Type, object>[];
        if(rules1 != null && rules1.Length > 0)
            Console.WriteLine("callsite1-Rules:" + rules1[0].GetHashCode());
        if (rules2 != null && rules2.Length > 0)
            Console.WriteLine("callsite3-Rules:" + rules2[0].GetHashCode());
        var binderCache1 =
            CallSiteContainer.CallSite1.Binder.GetType()
                .GetField("Cache", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(CallSiteContainer.CallSite1.Binder) as Dictionary<Type, object>;
        var binderCache2 =
            CallSiteContainer.CallSite3.Binder.GetType()
                .GetField("Cache", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(CallSiteContainer.CallSite3.Binder) as Dictionary<Type, object>;
        var binderCache3 =
            CallSiteContainer.CallSite4.Binder.GetType()
                .GetField("Cache", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(CallSiteContainer.CallSite4.Binder) as Dictionary<Type, object>;
        var binderCache4 =
            CallSiteContainer.CallSite5.Binder.GetType()
                .GetField("Cache", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(CallSiteContainer.CallSite5.Binder) as Dictionary<Type, object>;
        if (binderCache1 != null)
        {
            Console.WriteLine("callsite1-Binder-Cache:");
            foreach (var o2 in binderCache1)
            {
                Console.WriteLine("Key-Type:{0},Key-Value:{1},Value-Type:{2},Value-Value:{3}",o2.Key.Name,o2.Key.GetHashCode(),o2.Value.GetType().Name,o2.Value.GetHashCode());
            }
        }
        if (binderCache2 != null)
        {
            Console.WriteLine("callsite3-Binder-Cache:");
            foreach (var o2 in binderCache2)
            {
                Console.WriteLine("Key-Type:{0},Key-Value:{1},Value-Type:{2},Value-Value:{3}", o2.Key.Name, o2.Key.GetHashCode(), o2.Value.GetType().Name, o2.Value.GetHashCode());
            }
        }
        if (binderCache3 != null)
        {
            Console.WriteLine("callsite4-Binder-Cache:");
            foreach (var o2 in binderCache3)
            {
                Console.WriteLine("Key-Type:{0},Key-Value:{1},Value-Type:{2},Value-Value:{3}", o2.Key.Name, o2.Key.GetHashCode(), o2.Value.GetType().Name, o2.Value.GetHashCode());
            }
        }
        if (binderCache4 != null)
        {
            Console.WriteLine("callsite5-Binder-Cache:");
            foreach (var o2 in binderCache4)
            {
                Console.WriteLine("Key-Type:{0},Key-Value:{1},Value-Type:{2},Value-Value:{3}", o2.Key.Name, o2.Key.GetHashCode(), o2.Value.GetType().Name, o2.Value.GetHashCode());
            }
        }
测试结果如下:

3.动态行为实现
C#中要想实现动态行为,需要实现IDynamicMetaObjectProvider接口,在DLR中也提供了两个默认的实现:ExpandoObject和DynamicObject。
3.1ExpandoObject
ExpandoObject类可以在运行时动态地操作(包括添加、删除、赋值和取值等)成员,由于实现了IDynamicMetaObjectProvider接口,使得它可以在支持DLR互操作性模型的各种语言之间共享ExpandoObject类的实例。
class Program
{
    static void Main(string[] args)
    {
        // Use dynamic keyword to enable late binding for an instance of the ExpandoObject Class
        dynamic sampleObject = new ExpandoObject();
        // Add number field for sampleObject
        sampleObject.number = 10;
        Console.WriteLine(sampleObject.number);
        // Add Increase method for sampleObject
        sampleObject.Increase = (Action) (() => { sampleObject.number++; });
        sampleObject.Increase();
        Console.WriteLine(sampleObject.number);
        // Create a new event and initialize it with null.
        sampleObject.sampleEvent = null;
        // Add an event handler.
        sampleObject.sampleEvent += new EventHandler(SampleHandler);
        // Raise an event for testing purposes.
        sampleObject.sampleEvent(sampleObject, new EventArgs());
        // Attach PropertyChanged Event
        ((INotifyPropertyChanged)sampleObject).PropertyChanged += Program_PropertyChanged;
        sampleObject.number = 6;
        // Delete Increase method for sampleObject
        Console.WriteLine("Delete Increase method:" +
                          ((IDictionary<string, object>) sampleObject).Remove("Increase"));
        //sampleObject.Increase();// Throw a exception of which sampleObject don't contain Increase Method
        Console.ReadKey();
    }
    static void Program_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine("{0} has changed", e.PropertyName);
    }
    private static void SampleHandler(object sender, EventArgs e)
    {
        Console.WriteLine("SampleHandler for {0} event", sender);
    }
}
运行结果如下:

3.2DynamicObject
DynamicObject类与DLR的交互比ExpandoObject类更加细粒度,它能够定义在动态对象上哪些操作可以执行以及如何执行。由于它的构造函数是Protected的,所以无法直接new,需要继承该类。
// The class derived from DynamicObject.
    public class DynamicDictionary : DynamicObject
    {
        // The inner dictionary.
        Dictionary<string, object> dictionary
            = new Dictionary<string, object>();
        // This property returns the number of elements
        // in the inner dictionary.
        public int Count
        {
            get
            {
                return dictionary.Count;
            }
        }
        // If you try to get a value of a property
        // not defined in the class, this method is called.
        public override bool TryGetMember(
            GetMemberBinder binder, out object result)
        {
            // Converting the property name to lowercase
            // so that property names become case-insensitive.
            string name = binder.Name.ToLower();
            // If the property name is found in a dictionary,
            // set the result parameter to the property value and return true.
            // Otherwise, return false.
            return dictionary.TryGetValue(name, out result);
        }
        // If you try to set a value of a property that is
        // not defined in the class, this method is called.
        public override bool TrySetMember(
            SetMemberBinder binder, object value)
        {
            // Converting the property name to lowercase
            // so that property names become case-insensitive.
            dictionary[binder.Name.ToLower()] = value;
            // You can always add a value to a dictionary,
            // so this method always returns true.
            return true;
        }
    }
	static void Main(string[] args)
	{
		// Creating a dynamic dictionary.
        dynamic person = new DynamicDictionary();
        // Adding new dynamic properties.
        // The TrySetMember method is called.
        person.FirstName = "Ellen";
        person.LastName = "Adams";
        // Getting values of the dynamic properties.
        // The TryGetMember method is called.
        // Note that property names are case-insensitive.
        Console.WriteLine(person.firstname + " " + person.lastname);
        // Getting the value of the Count property.
        // The TryGetMember is not called,
        // because the property is defined in the class.
        Console.WriteLine(
            "Number of dynamic properties:" + person.Count);
        // The following statement throws an exception at run time.
        // There is no "address" property,
        // so the TryGetMember method returns false and this causes a
        // RuntimeBinderException.
        // Console.WriteLine(person.address);
	}
运行结果如下:

3.3IDynamicMetaObjectProvider
如果你只是在运行时简单地做一些动态的操作,可以使用ExpandoObject类;如果你想稍微深入一些,在动态对象上定义哪些操作可以执行以及如何执行,可以使用DynamicObject;如果你想更完全的控制动态对象的行为的话,你可以实现IDynamicMetaObjectProvider接口。使用IDynamicMetaObjectProvider接口是在一个比DynamicObject类更低级地对DLR库的依赖。DLR使用可扩展的表达式树来实现动态行为。
public class DynamicDictionary : IDynamicMetaObjectProvider
    {
        #region IDynamicMetaObjectProvider Members
        public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return new DynamicDictionaryMetaObject(parameter, this);
        }
        #endregion
        private class DynamicDictionaryMetaObject : DynamicMetaObject
        {
            public DynamicDictionaryMetaObject(Expression expression, object value)
                : base(expression, BindingRestrictions.Empty, value)
            {
            }
            public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
            {
                // Method to call in the containing class
                string methodName = "SetDictionaryEntry";
                // Setup the binding restrictions
                BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
                // Setup the parameters
                Expression[] args = new Expression[2];
                // First parameter is the name of the property to set
                args[0] = Expression.Constant(binder.Name);
                // Second parameter is the value
                args[1] = Expression.Convert(value.Expression, typeof(object));
                // Setup the 'this' reference
                Expression self = Expression.Convert(Expression, LimitType);
                // Setup the method call expression
                Expression methodCall = Expression.Call(self, typeof(DynamicDictionary).GetMethod(methodName), args);
                // Create a meta objecte to invoke set later
                DynamicMetaObject setDictionaryEntry = new DynamicMetaObject(methodCall, restrictions);
                return setDictionaryEntry;
            }
            public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
            {
                // Method call in the containing class
                string methodName = "GetDictionaryEntry";
                // One parameter
                Expression[] parameters = new Expression[]
            {
                Expression.Constant(binder.Name)
            };
                // Setup the 'this' reference
                Expression self = Expression.Convert(Expression, LimitType);
                // Setup the method call expression
                Expression methodCall = Expression.Call(self,
                    typeof(DynamicDictionary).GetMethod(methodName), parameters);
                // Setup the binding restrictions
                BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
                DynamicMetaObject getDictionaryEntry = new DynamicMetaObject(methodCall, restrictions);
                return getDictionaryEntry;
            }
            public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
            {
                StringBuilder paramInfo = new StringBuilder();
                paramInfo.AppendFormat("Calling {0}(", binder.Name);
                foreach (var item in args)
                {
                    paramInfo.AppendFormat("{0}, ", item.Value);
                }
                paramInfo.Append(")");
                Expression[] parameters = new Expression[]
                {
                    Expression.Constant(paramInfo.ToString())
                };
                Expression self = Expression.Convert(Expression, LimitType);
                Expression methodCall = Expression.Call(self, typeof(DynamicDictionary).GetMethod("WriteMethodInfo"),
                    parameters);
                BindingRestrictions restrictions = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
                return new DynamicMetaObject(methodCall, restrictions);
            }
        }
        private Dictionary<string, object> storage = new Dictionary<string, object>();
        public object SetDictionaryEntry(string key, object value)
        {
            if (storage.ContainsKey(key))
            {
                storage[key] = value;
            }
            else
            {
                storage.Add(key, value);
            }
            return value;
        }
        public object GetDictionaryEntry(string key)
        {
            object result = null;
            if (storage.ContainsKey(key))
            {
                result = storage[key];
            }
            return result;
        }
        public object WriteMethodInfo(string methodInfo)
        {
            Console.WriteLine(methodInfo);
            return 42;// because it is the answer to everything
        }
        public override string ToString()
        {
            StringWriter writer = new StringWriter();
            foreach (var o in storage)
            {
                writer.WriteLine("{0}:\t{1}", o.Key, o.Value);
            }
            return writer.ToString();
        }
    }
调用如下:
static void Main(string[] args)
    {
        dynamic dynamicDictionary = new DynamicDictionary();
        dynamicDictionary.FirstName = "jello";
        dynamicDictionary.LastName = "chen";
        dynamicDictionary.Say();
        Console.WriteLine(dynamicDictionary.FirstName);
        Console.ReadKey();
    }
结果如图所示:

4.实例剖析:Javascript DLR Engine
Javascript DLR Engine是CodePlex上的一个开源项目,它是构建在DLR上层的Javascript引擎,还有RemObjects
也是如此。需要注意的是,Javascript DLR Engine只是对ECMAScript 3语言部分特性的实现。下面是我Download的Javascript DLR Engine项目截图:

这里大概讲讲流程:
1.首先,将JavaScriptContext这个自定义的LanguageContext注册到ScriptRuntime
2.接着,获取ScriptEngine对象,由于会将ScriptEngine对象缓存在ScriptRuntime对象中,所以第一次需要new一个ScriptEngine对象并缓存
3.接着,创建ScriptScope对象(相当于一个命名空间)
4.通过调用ScriptEngine.ExecuteFile方法执行脚本文件,内部会去调用JavaScriptContext的CompileSourceCode重写方法获取ScriptCode对象
5.在JavaScriptContext的CompileSourceCode的重写方法中,使用ANTLRL来解析成AST(Expression Tree),用Expression构造一个名为InterpretedScriptCode的ScriptCode对象
6.接着调用InterpretedScriptCode对象的Run方法,然后交由Interpreter类去处理执行表达式
C#中的动态特性的更多相关文章
- [.NET] 《Effective C#》快速笔记 - C# 中的动态编程
		<Effective C#>快速笔记 - C# 中的动态编程 静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作.C# 是一种静 ... 
- 《Effective C#》快速笔记(五)-  - C# 中的动态编程
		静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作.C# 是一种静态类型的语言,不过它加入了动态类型的语言特性,可以更高效地解决问题. 一. ... 
- Java语言中的面向对象特性总结
		Java语言中的面向对象特性 (总结得不错) [课前思考] 1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类? 2. 面向对象编程的特性有哪三个?它们各自又有哪些特性? 3. 你知 ... 
- 深入理解javascript中的动态集合——NodeList、HTMLCollection和NamedNodeMap
		× 目录 [1]NodeList [2]HTMLCollection [3]NamedNodeMap[4]注意事项 前面的话 一说起动态集合,多数人可能都有所了解.但是,如果再深入些,有哪些动态集合, ... 
- iOS 深入Objective-C的动态特性
		深入Objective-C的动态特性 Objective-C具有相当多的动态特性,基本的,也是经常被提到和用到的有动态类型(Dynamic typing),动态绑定(Dynamic binding)和 ... 
- iOS 动态特性和RunTime
		过去的几年中涌现了大量的Objective-C开发者.有些是从动态语言转过来的,比如Ruby或Python,有些是从强类型语言转过来的,如Java或C#,当然也有直接以Objective-C作为入门语 ... 
- OC动态特性
		今天是2.15周日,快要过年了,我以一个实习生的身份在公司工作了快要两个月了吧,什么是北漂,北漂就是感觉生活节奏变了,以前困了可以上课睡觉,累了可以回家休息数周,人际交往乏了,可以躲起来看着窗外的雨或 ... 
- Indri中的动态文档索引技术
		Indri中的动态文档索引技术 戴维 译 摘要: Indri 动态文档索引的实现技术,支持在更新索引的同时处理用户在线查询请求. 文本搜索引擎曾被设计为针对固定的文档集合进行查询,对不少应用来说,这种 ... 
- JS动态特性
		var arr=[]; arr['js']='jquery'; arr['css']='oocss'; var obj={}; for(var i in arr) { obj[i]=arr[i]; } ... 
随机推荐
- java中由类名和方法名字符串实现其调用【反射机制】
			js里通过eval()函数,在知道某个方法名是可以实现调用该方法,那么在java里边又怎么实现的呢? java里边是通过反射机制来实现,代码如下: import java.lang.reflect.M ... 
- WPF命中测试示例(一)——坐标点命中测试
			原文:WPF命中测试示例(一)--坐标点命中测试 命中测试也可被称为碰撞测试,在WPF中使用VisualTreeHelper.HitTest()方法实现,该方法用于获取给定的一个坐标点或几何形状内存在 ... 
- jquery再体验
			$(function(){ var obj = $("div[id^='channel_'][id$='_left']"); var val = obj.html(); var i ... 
- Android开发之Buidler模式初探结合AlertDialog.Builder解说
			什么是Buidler模式呢?就是将一个复杂对象的构建与它的表示分离,使得相同的构建过程能够创建不同的表示.Builder模式是一步一步创建一个复杂的对象,它同意用户能够仅仅通过指定复杂对象 ... 
- java + memcached安装
			一:安装 (临时获取上手windows实验) 1.下载memcached.exe , 上F:\memcached\ 下 2.在CMD在输入 "F:\memcached\memcached.e ... 
- ftk学习记(消息框篇)
			[ 声明:版权全部,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 上一篇说到了输入框.闲话不多说,首先看结果显示, 大家看看效果是不是和我们之前说的一样.今天, ... 
- Preference如何增加在activity生命周期监听器
			转载请注明出处:http://blog.csdn.net/droyon/article/details/41313115 本文主要介绍Preference凭什么Activit一些逻辑的生命周期,使. ... 
- JavaScript动态更改页面元素
			通过JavaScript动态变化HTML元素 至HTML加元 首先需要创建一个标签,然后添加到标签中的相应的内容.然后创建添加到相应的位置好标签. <!DOCTYPE html PUBLIC & ... 
- Springmvc +JNDI 在Tomcat下 配置数据源(转)
			一. 简介 jndi(Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API.命名服务 ... 
- 采用PopupWin控制消息推送功能
			最近的项目需要,急需实现消息推送功能.接连试了很多办法,让我们用JavaScript为了实现啊,其效果是不咋好,最后,我发现了一个PopupWin这个,看着眼前的成绩不错,开始使用. 1.准备工作.先 ... 
