导航

C# 泛型

一、前言

首先谈谈泛型,包括Java, C++都有自己的泛型(模版),这种机制大大的减少了代码的数量,是一种类型的抽象。集合就我了解C++的 STL 中的vector<T>, list<T>, map<T,T> 等, .net 中的List<T>, HashTable<T,T>等,都是对基本数据结构的实现,如链表,队列,栈,等。但是在具体使用中,不同的语言,如果使用不当,我造成严重的性能影响,合格的程序员应该了解这些性能陷阱。

 

二、.net 泛型

在.net中通过使用泛型,我们可以达到以下两个目的:

1.Type safe  2. No Boxing.

这个比较好理解,举个例子ArrayList, 其源码如下:

public class ArrayList : IEnumerable, ICollection, IList
    {
        private object[] items;
        private int size;
        public ArrayList(int initalCapacity)
        {
            items = new object[initalCapacity];
        }

        public void Add(object item)
        {
            if (size < items.Length - 1)
            {
                items[size] = item;
                ++size;
            }
            else {
                //Allocate a larger array, copy the elements to there.
            }
        }

        public object this[int index]
        {
            get
            {
                if (index < 0 || index >= size) throw new IndexOutOfRangeException();
                return items[index];
            }
            set
            {
                if (index < 0 || index >= size) throw new IndexOutOfRangeException();
            }
        }

        // ommit other details

    }

可见在Add的时候会有装箱操作发生,如果存放的是1,000,000的Point, 将会有大量的内存被浪费掉(8M (extra)+ 8M(data) + 4M(reference)), 除了因为装箱引起内存浪费外,因为我们相关的操作时基于System.Object,类型安全也是一个大问题。

 

泛型可以完美的解决这个问题,原理看简化的源码:

public class List<T> : IEnumerable<T>, ICollection<T>, IList<T>
    {
        private T[] items;
        private int size;
        public List(int initalCapacity)
        {
            // does this work?
            items = new T[initalCapacity];
        }

        public void Add(T item)
        {
            if (size < items.Length - 1)
            {
                items[size] = item;
                ++size;
            }
            else
            {
                //Allocate a larger array, copy the elements to there.
            }
        }

        public T this[int index]
        {
            get
            {
                if (index < 0 || index >= size) throw new IndexOutOfRangeException();
                return items[index];
            }
            set
            {
                if (index < 0 || index >= size) throw new IndexOutOfRangeException();
                items[index] = value;
            }
        }

        // ommit other details

    }

不用担心类型安全和装箱拆箱的问题了。但是如果我增加一些比较的功能呢?

 public static int BinarySearch<T>(T[] array, T element) {

//At some point in the algorithm, we need to compare:

if (array[x] < array[y]) {

...

}

System.Object没有实现 static operator <, 对于上面这个函数,大部分都会类型都会编译错误。我们可以使用模版函数的限制功能,来保证T实现了 比较的 , .NET一种有5种限制:

public class Widget{
        public void Display(int i, int j) { }
    }

    public class GenericDemo
    {
        // T must implement an interface
        public string Format<T>(T instance) where T: IFormattable
        {
            return instance.ToString("N", CultureInfo.CurrentCulture);
            // OK, T must have IFormattable.ToString(...)
        }

        // T must based on a base class
        public void Display<T>(T widget) where T : Widget
        {
            widget.Display(1, 2);
        }

        // T must a parameterless cosntructor
        public T Create<T>() where T : new()
        {
            return new T();
        }

        // T must be a reference type:
        public void ReferencesOnly<T>(T reference) where T : class { }

        // T must be a value type:
        public void ValueType<T>(T valueType) where T : struct { }

 
}

这样我们可以这样写BinarySearch了: 

public static int BinarySearch<T>(T[] array, T element) where T : IComparable<T> {

        //At some point in the algorithm, we need to compare:

             int x = 1; int y = 2;

        if (array[x].CompareTo(array[y]) < 0) {

        //...

        }

        }

 

接下来我们再讨论 IEquatable<T>,先看下面这个函数:

public static void CallEquals<T>(T instance) {
instance.Equals(instance);
}

Equals将会调用基类的虚函数Equals,它的参数是System.Object,会产生装箱。但是我们实现了IEquatable<T>,使用在模版限定中, 就可以避免装箱了

//From the .NET Framework:
public interface IEquatable<T> {
bool Equals(T other);
}

public static void CallEquals<T>(T instance) where T : IEquatable<T> {
instance.Equals(instance);
}

这个函数将不再调用虚函数的Equals, 这样就避免了装箱。我们在前一篇文章中,提到valueType的最佳实践中,要现实IEquatable<T> 就是这个原因。 那么按理说所有的集合最好都限制为IEquatable<T>类型,但是为了扩展性的考虑,我们用组合的形式,委托给GenericEqualityComparer, 举个例子List<T>.Contains, 前看简化源码:

public bool Contains(T item)
        {
            if (item == null)
            {
                for (int i = 0; i < this._size; i++)
                {
                    if (this._items[i] == null)
                    {
                        return true;
                    }
                }
                return false;
            }
            EqualityComparer<T> @default = EqualityComparer<T>.Default;
            for (int j = 0; j < this._size; j++)
            {
                if (@default.Equals(this._items[j], item))
                {
                    return true;
                }
            }
            return false;
        }

把比较委托给了EqualityComaprer<T>, 如果我们替换了它,就可以改变我们的比较策略了。除了equals,这里还有别的实现数学的泛型知识。请看参考链接

 

三、参考

<<Pro .NET Performance>>

http://www.codeproject.com/Articles/8531/Using-generics-for-calculations

 

posted on 2015-05-31 16:36  水中游  阅读(141)  评论(0编辑  收藏  举报