资源描述
CSharp扩展方法扩展方法是实现新增类方法同时不改变类名称的一种技术。可以部分的替代原本必须继承类才能够实现的功能。但是,目前还不能扩展类的属性,也不能在不继承类的情况下而直接修改类方法。例如在A MVC Web Application中的,我们想快速了解某个Action上是否有某个Attribute. 那我们可以使用这样的扩展方法:/ / Gets the method./ / Type/ The instance./ The method selector./ MethodInfopublic static MethodInfo GetMethod(this T instance, ExpressionFunc methodSelector) / it is not work all method return (MethodCallExpression)methodSelector.Body).Method;/ / Gets the method./ / / The instance./ The method selector./ MethodInfopublic static MethodInfo GetMethod(this T instance, ExpressionAction methodSelector) return (MethodCallExpression)methodSelector.Body).Method;/ / Determines whether the specified member has attribute./ / The type of the attribute./ The member./ / true if the specified member has attribute; otherwise, false./ public static bool HasAttribute( this MemberInfo member) where TAttribute : Attribute return GetAttributes(member).Length 0;/ / Gets the attributes./ / The type of the attribute./ The member./ public static TAttribute GetAttributes( this MemberInfo member) where TAttribute : Attribute var attributes = member.GetCustomAttributes(typeof(TAttribute), true); return (TAttribute)attributes;如何使用,请看下面的代码,我们使用lambda表达式获取某个方法,然后获取其上面的Attribute:Factpublic void GetHttpPostAttributeFromCreateAction() / Arrange var controller = GetEmployeeController(new MemeoryEmployeeBoService(); /Act bool hasPostAttribute =controller.GetMethod(e = e.Create(new Employee() .HasAttribute(); / Assert Assert.True(hasPostAttribute);扩展方法的实现1.首先,我们需要在项目中添加自己的一个类型,此处为ProjectExt,其中的方法与之前定义的方法大致上无区别,只是在方法中第一个参数前面添加了this关键字,this关键字必须是在方法参数中第一个参数的前面,不可以移位。view sourceprint?01.public static class ProjectExt02. 03. / 04. / 向字符串追加字符05. / 06. / 字符串07. / 需要追加的字符串08. / 追加完成的字符对象09. public static string AppendToLeft(this string obj,string AppendStr)10. 11. return AppendStr + obj;12. 13. 2.像在调用FCL本身类库的方法一样调用AppendToLeft,我们的代码看起来更美丽整洁!view sourceprint?1.static void Main(string args)2.3. string Tel = 87763445;4. Console.WriteLine(Tel:+ Tel.AppendToLeft(020-);5.输出:020-87763445我们可以浅而易懂的看到两者之间的区别,这就是扩展方法带来的便捷之处,让我们的代码更加的整洁,在使用扩展方法的时候必须注意一下几点:1.第一个参数之前必须有this关键字,不可变动2.在添加扩展方法的类型中,类型本身和类型所包含的方法必须是静态的。3.由于这里是按类型识别,所以不应该将扩展方法的第一个参数类型设为object,这样会造成无论使用什么类型,都会扩展该方法。4.必须考虑将来FCL可能会添加相同的方法,如果FCL在以后的版本中添加了该方法,那么C#会优先选择FCL所定义的方法。最后的补充:扩展方法为什么要添加一个this在前面呢?这引用类型的特性有关,this指向调用者本身,所以改变的也是调用者本身的实例,至于为什么在Visual Studio中能找到该方法,这是C#编辑器的功能,其中有一个搜索的过程,如果要使用不同命名空间下的扩展方法,请在代码顶端using该命名空间。另外,由于结构(struct)不能声明为静态,但是扩展方法必须声明在静态类型之中,所以不能在结构(struct)中声明扩展方法,这点必须要注意!我们在开发经常要使用Enum类型,今天我们用扩展方法来为Enum类型加入业务逻辑. 有以下的代码: 1: / 2: / StorageProviders 3: / 4: Serializable 5: public enum StorageProviders 6: 7: / 8: / LuceneIo 9: / 10: LuceneIo = 0, 11: / 12: / LuceneVirtual 13: / 14: LuceneVirtual = 1 15: 然后写一个扩展方法: 1: / 2: / StorageProvidersExtensions 3: / 4: public static class StorageProvidersExtensions 5: 6: / 7: / Determines whether the specified provider is virtual. 8: / 9: / The provider. 10: / 11: / true if the specified provider is virtual; otherwise, false. 12: / 13: public static bool IsVirtual(this StorageProviders provider) 14: 15: return provider = StorageProviders.LuceneVirtual; 16: 17: 好了,让我们来看如何使用: 1: Test 2: public void TestEnumExtesnsionMethod() 3: 4: StorageProviders storageProviders = StorageProviders.LuceneVirtual; 5: Assert.IsTrue(storageProviders.IsVirtual(); 6: 为对象添加扩展属性动态获取数据 由于项目需要常常会遇到为某一个对象动态添加属性的情况,而以前我的实现方式是创建一个字典用于存放对象实例和它的值,但是往往光这么做是不够的,例如想在对象的某个属性值改变的时候做点什么都要写很多的代码,所以想是不是能够将这一类功能进行一下封装。后来因为学习WPF的缘故,想到依赖属性的思想和我需要的功能相近,但是又不能叫我把每一个想要添加扩展的对象类都去继承DependencyObject吧,而且有些类是封闭的不能够继承,所以依赖属性不能满足我的需求。不过说到底依赖属性还是个不错的东西,接下来我们将实现一个类似的东西 - 扩展属性。在实现扩展属性时我也参考了依赖属性的源码,它的设计思想的确很“先进”。1.先来看看扩展属性的使用方式:1: private static ExtendProperty InfoProperty = 2: ExtendProperty.RegisterProperty(Info, typeof(string), typeof(UserInfo),you win); 3: var user = new UserInfo() Age=21, Name=maxzhang ; 4: 5: user.SetValue(InfoProperty, hello); 6: string rrr = (string)user.GetValue(InfoProperty);是不是看着特别像依赖属性呢,往下面看:1: dynamic userDynamic = user.AsDynamic(); 2: rrr= userDynamic.Info; 3: userDynamic.Info = 1; 4: userDynamic.Age = 50; 5: rrr = userDynamic.Info;我为扩展属性添加了动态性使对象属性的创建和访问更加方便,这里如果Info属性在前面没有用RegisterProperty方法定义过它会自动生成一个扩展属性且添加属性值.如果访问了它的普通属性属性也是正常使用的。以上两个例子中UserInfo类的定义是 public class UserInfo : ExtendObject public string Name set; get; public int Age set; get; ,你可能会问这不是和依赖属性一样吗?只是把继承DependencyObject换成了继承你自己写的ExtendObject 了。是的这样看是差不多的,不过以上的情况还是有一个好处的就是我可以在任何项目里引用它。如果遇到了不能继承的情况呢,其实这种情况有很多。接 public class UserInfo1 public string Nameset;get; 这个类不继承任何类。解决它这里引入了新的扩展类型AttachObject :1: AttachObject user1Aobj = new AttachObject(user1); 2: var dyuser = user1Aobj.ToDynamicAttachObject(); 3: /var dyuser = user1.ToDynamicAttachObject(); 4: dyuser.Memo = haha my name is maxzhang.; 5: rrr = dyuser.Memo;其实AttachObject 类型也是一个ExtendObject 可以把它看成是一个ExtendObject 的装饰。2.下面我们来看看这些都是怎么实现的(1).ExtendProperty与依赖属性类似,在ExtendProperty类中用了一个Dictionary来存储系统中要用到的扩展属性,这样实现也达到了节省内存资源的目地。且这个类的构造器是一个private的,这样也就实现了一个单例模式,只有在RegisterProperty方法才能创造出一个ExtendProperty来.RegisterPropertypublic static ExtendProperty RegisterProperty(string propertyName, Type propertyType, Type ownerType,object defaultValue)var property = new ExtendProperty(propertyName, propertyType,ownerType);property.OverrideDefaultValue(ownerType, defaultValue);ExtendPropertysProvider.Set(property.GetHashCode(), property);return property;用GetHashCode来标示我们这个属性的唯一性,这里我重写了这个函数它的值是this.ownerType.GetHashCode()this.propertyName.GetHashCode(),也就是说用注册这个属性的类型和属性的名称确定了这个扩展属性。我们看到OverrideDefaultValue这个方法它是用来重写属性的默认值的,在这个系统中如果某个对象的扩展属性没有赋过值或说没有改变过,那么它应该在访问这个属性的时候取得一个默认值而且这个默认值应该是所有相同注册类型的对象共有的,而在用普通属性存储的对象中我们实例化对象后会在每一个对象中保存相应的默认值,这样无疑是浪费了内存。而且OverrideDefaultValue与AddOwner方法一起使用可以达到属性继承的目的。我们来看看AddOwner方法的实现:AddOwnerpublic ExtendProperty AddOwner(Type ownerType,object defaultValue)int newOwnerHash = ownerType.GetHashCode() this.PropertyName.GetHashCode();if(defaultValue!=null)this.OverrideDefaultValue(ownerType, defaultValue);ExtendPropertysProvider.Set(newOwnerHash, this);return this;使用AddOwner方法我们就在原有的扩展属性上添加了一个指向它的引用从而达到继承的目地,怎么重写属性默认值呢?其实很简单默认值在扩展属性中保存在一个的字典中通过不同的类型我们就可以访问不同类型的相同属性的默认值了。 (2).ExtendObject 这里ExtendObject就没什么好说的了,原理就是其内部有一个Dictionary propertyValues 存储着不同对象的值,用自身的GetHashCode 扩展属性的HashCode 确定值的唯一性。ExtendObject的源码,呵呵 public class ExtendObjectprotected Dictionary propertyValues = new Dictionary();private Type OwnerType = null;public ExtendObject()OwnerType = this.GetType();public override int GetHashCode()return base.GetHashCode();public virtual object GetOwner()return this;protected void AttachOwner(Type ownerType)this.OwnerType = ownerType;public bool IsExtendProperty(string propertyName)return !OwnerType.GetProperties().Any(p = p.Name = propertyName); ;protected ExtendProperty GetProperty(string name)int propertyKey = OwnerType.GetHashCode() name.GetHashCode();var property = ExtendPropertysProvider.Get(propertyKey);return property;public object GetValue(ExtendProperty property)int propertyHash = property.GetHashCode();int key = this.GetHashCode() propertyHash;object result = null;if (!propertyValues.TryGetValue(key, out result)result = property.GetDefaultValue(this.OwnerType);return result;public bool ClearValue(ExtendProperty property)bool result = false;int propertyHash = property.GetHashCode();int key = this.GetHashCode() propertyHash;if (propertyValues.Keys.Any(k = k = key)propertyValues.Remove(key);result = true;return result;public void SetValue(ExtendProperty property, object value)var changedItemArgs = new ExtendPropertyValueChangedArgs();int propertyHash = property.GetHashCode();int key = this.GetHashCode() propertyHash;if (propertyValues.Keys.Any(k = k = key)changedItemArgs.OldValue = propertyValueskey;propertyValueskey = value;elsechangedItemArgs.OldValue = null;propertyValues.Add(key, value);changedItemArgs.Item = GetOwner();changedItemArgs.PropertyType = property.PropertyType;changedItemArgs.PropertyName = property.PropertyName;changedItemArgs.NewValue = value;property.OnValueChanged(changedItemArgs);public bool ClearValue(string propertyName)var property = this.GetProperty(propertyName);if (property != null)return this.ClearValue(property);return false;public object GetValue(string propertyName)var property = this.GetProperty(propertyName);if (property != null)return this.GetValue(property);return null;public void SetValue(string propertyName, object value)var property = this.GetProperty(propertyName);if (property != null)this.SetValue(property, value);elsevar newProperty = ExtendProperty.RegisterProperty(propertyName, typeof(object), OwnerType);this.SetValue(newProperty, value);public ExtendDynamicObject AsDynamic()return new ExtendDynamicObject(this);不过这里还是有一个小小的技巧的就是OwnerType这个属性和AttachOwner方法,默认的OwnerType属性的值是扩展对象本身的Type,但是通过 AttachOwner方法我们可以改变这个属性从而达到将不继承自ExtendObject类型的对象装饰成ExtendObject对象的目地。(3).也就是AttachObjectAttachObject类通过调用AttachOwner方法使用了这个技巧,同时把同样为ExtendObject的对象的属性统统都Copy过来.AttachObjectpublic class AttachObject : ExtendObjectprivate object owner;public AttachObject(object obj): base()owner = obj;if (owner is ExtendObject)Type ownerType = typeof(ExtendObject);FieldInfo fInfo = ownerType.GetField(propertyValues, BindingFlags.Default | BindingFlags.NonPublic | BindingFlags.Instance);var ownerValues = fInfo.GetValue(owner) as Dictionary;foreach (var v in ownerValues)this.propertyValues.Add(v.Key, v.Value);this.AttachOwner(owner.GetType();public override object GetOwner()return owner;public override int GetHashCode()return owner.GetHashCode();
展开阅读全文