- Mastering Windows Presentation Foundation
- Sheridan Yuen
- 1668字
- 2021-06-24 16:49:06
With Extension Methods
There is a further method of providing additional functionality to the developers of our application that was mentioned when investigating the application structures in the Chapter 2, Debugging WPF Applications. It is through the use of Extension Methods. If you are not familiar with this amazing .NET feature, Extension Methods enable us to write methods that can be used on objects that we did not create.
At this stage, it's worth pointing out that we don't generally write Extension Methods for classes that we have declared. There are two main reasons for this. The first is that we created these classes and so we have access to their source code and can therefore simply declare new methods in these classes directly.
The second reason is that there will be a reference to our Extensions project added to most other projects, including our DataModels project, so that they can all take advantage of the extra capabilities. Therefore, we can't add references to any of our other projects into the Extensions project, because it would create circular dependencies.
You are probably aware of Extension Methods already, although perhaps inadvertently, as most of the LINQ methods are Extension Methods. Once declared, they can be used just like the ordinary methods that were declared within the various classes that we are extending, although they are differentiated by having different icons in the Visual Studio IntelliSense display:
The basic principle when declaring them is to have a static class, where each method has an extra input parameter prefixed with the this keyword, that represents the object being extended. Note that this extra input parameter must be declared first in the parameter list and that it will not be visible in IntelliSense when calling the method on an instance of an object.
Extension Methods are declared as static methods, but are typically called using instance method syntax. A simple example should help to clarify this situation. Let's imagine that we want to be able to call a method on each item in a collection. In fact, we'll see an example of this being used in our BaseSynchronizableCollection class later in this chapter, but now, let's see how we can do this:
using System; using System.Collections.Generic; namespace CompanyName.ApplicationName.Extensions { public static class IEnumerableExtensions { public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action) { foreach (T item in collection) action(item); } } }
Here, we see the this input parameter that specifies the instance of the target type that this Extension Method is called on. Remember that this won't appear in the parameter list in IntelliSense in Visual Studio, unless it is called through the static class itself, as shown in the following code:
IEnumerableExtensions.ForEach(collection, i => i.RevertState());
Inside this method, we simply iterate through the collection items, calling the Action specified by the action input parameter and passing in each item as its parameter. After adding a using directive to the CompanyName.ApplicationName.Extensions namespace, let's see how this method is more usually called:
collection.ForEach(i => i.PerformAction());
So, you can now see the power of Extension Methods and the benefits that they can bring us. If some functionality that we want is not already provided by a certain class in the .NET Framework, then we can simply add it. Take this next example.
Here is an Extension Method that has been sorely missed from the existing LINQ Extension Methods. As with the other LINQ methods, this one also works on the IEnumerable<T> interface and, therefore, also any collection that extends it:
public static IEnumerable<TSource> DistinctBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) { HashSet<TKey> keys = new HashSet<TKey>(); foreach (TSource element in source) { if (keys.Add(keySelector(element))) yield return element; } }
Let's first look at the declaration of this method. We can see that our source collection will be of type TSource. Note that this is exactly the same as if the generic type parameter were named T, like in our other examples, except that this provides a little more detail as to the use of this type parameter. This naming has come from the Enumerable.OrderBy<TSource, TKey> method, where type TSource parameter represents our source collection.
Next, we notice that the method name is suffixed by two generic type parameters; first, the TSource parameter, and then the TKey parameter. This is because we require two generic type parameters for the input parameter of type Func<TSource, TKey>. If you're not familiar with the Func<T, TResult> delegate, as Microsoft calls it, it simply encapsulates any method that has a single input parameter of type T and returns a value of type TResult, or, in our case, TKey.
"Why are we using this Func<T, TResult> delegate?", I hear you asking. Well, it's simple really; using this class, we can provide the developers with an object of the same type as those in the source collection and the ability to select a member of that class, in particular, the property that they want to perform the distinct query on. Before looking at the rest of this method, let's see it in use:
IEnumerable<User> distinctUsers = Users.DistinctBy(u => u.Id);
Let's envisage that we had a collection of User objects that had all purchased items. This collection could contain the same User object more than once, if they purchased more than one item. Now, let's imagine that we wanted to compile a collection of unique users from the original collection, so as not to send multiple bills to people that ordered multiple items. This method would return a single member for each distinct Id value.
Referring back to the source code for this method, the User class represents the TSource parameter and this is shown in the Lambda expression in the example as the u input parameter. The TKey parameter is determined by the type of the class member that is selected by the developer, in this case, by the Guid Id value. This example could be written slightly differently to make it clearer:
IEnumerable<User> distinctUsers = Users.DistinctBy((User user) => user.Id);
So, our Func<TSource, TKey> can be seen here, with a User input parameter and a Guid return value. Now, let's focus on the magic of our method. We see a HashSet of type Guid in our case being initialized. This type of collection is essential to this method, as it allows only unique values to be added.
Next, we iterate through our source collection, of type User in this case, and attempt to add the relevant property value of each item in the collection into the HashSet. In our case, we're adding the values of the identities of each User object into this HashSet.
If the identity value is unique and the HashSet<T>.Add method returns true, we yield, or return that item from our source collection. The second and each subsequent time that a used Id value is read, it is rejected. This means that only the first items with unique identity values are returned from this method. Note that in this example, we are not interested in the purchases, but in the unique users that made them.
We've now managed to create our very own LINQ-style Extension Method. However, not all of our Extension Methods need to be so ground breaking. Often, they can be used to simply encapsulate some commonly used functionality.
In a way, we can use them as simple convenience methods. Take a look at the following example that is used in the With Converters section later in this chapter:
using System;
using System.ComponentModel;
using System.Reflection;
namespace CompanyName.ApplicationName.Extensions { public static class EnumExtensions { public static string GetDescription(this Enum value) { FieldInfo fieldInfo = value.GetType().GetField(value.ToString()); if (fieldInfo == null) return Enum.GetName(value.GetType(), value); DescriptionAttribute[] attributes = (DescriptionAttribute[]) fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes != null && attributes.Length > 0) return attributes[0].Description; return Enum.GetName(value.GetType(), value); } } }
In this method, we attempt to get the FieldInfo object that relates to the instance of the relevant enumeration provided by the value input parameter. If the attempt fails, we simply return the name of the particular instance. If we succeed however, we then use the GetCustomAttributes method of that object, passing the type of the DescriptionAttribute class, to retrieve an array of attributes.
If we have declared a value in the DescriptionAttribute of this particular enumeration instance, then it will always be the first item in the attribute array. If we have not set a value, then the array will be empty and we return the name of the instance instead. Note that as we used the base Enum class in this method, we are able to call this method on any enumeration type.
When creating these methods, it should be noted that there is no requirement to put them into separate classes that are split by type, as we have done here. There are no specified naming conventions either and, in fact, it is also totally viable to put all of your Extension Methods into a single class. However, if we have a large number of Extension Methods of a particular type, then it can help with maintenance to have this separation.
Before moving on, let's take a look at one final example of these Extension Methods. One of the most useful traits of an Extension Method is the ability to add new or missing functionality to existing classes from the .NET Framework. For example, let's see how we can replicate Linq and define a simple Count method for the IEnumerable class:
public static int Count(this IEnumerable collection) { int count = 0; foreach (object item in collection) count++; return count; }
As we can see, this method requires little explanation. It literally just counts the number of items in the IEnumerable collection and returns that value. As simple as it is, it proves to be useful, as we'll see in a later example. Now that we have investigated Extension Methods, let's turn our attention to another way of building further abilities into our framework, this time focusing on the Views component.
- 從零構建知識圖譜:技術、方法與案例
- 大學計算機應用基礎實踐教程
- Testing with JUnit
- JIRA 7 Administration Cookbook(Second Edition)
- 學Python也可以這么有趣
- C#程序設計
- Swift 4 Protocol-Oriented Programming(Third Edition)
- Python大學實用教程
- PHP 7從零基礎到項目實戰
- Java程序設計與項目案例教程
- 進入IT企業必讀的324個Java面試題
- Practical Predictive Analytics
- Web開發的平民英雄:PHP+MySQL
- UML基礎與Rose建模實用教程(第三版)
- Microsoft Dynamics GP 2013 Cookbook