把C#内部的List
IList
namespace StructScript
{
public interface IList : ICollection
{
///
/// 提供读取和编辑列表中的项的方法
///
///
///
T this[int index]
{
get;
set;
}
///
/// 搜索指定的对象,并返回第一个匹配项的索引
/// 如果项不在列表中,返回-1
///
int IndexOf(T value);
///
/// 如果项在列表中,返回特定项的索引
/// 如果项不在列表中,返回-1
///
int LastIndexOf(T value);
///
/// 插入一个值到列表的一个固定位置
/// 插入位置必须小于等于列表项的总数,且大于0
///
///
///
void Insert(int index, T value);
///
/// 移除索引位置的项
///
void RemoveAt(int index);
///
/// 列表中所有元素的顺序反转
///
void Reverse();
///
/// 使用默认比较器对所有元素进行排序
///
void Sort();
}
}
IConnection
using System.Collections.Generic;
namespace StructScript
{
public interface ICollection : IEnumerable
{
///
/// 返回列表中项的总数
///
int Count { get; }
///
/// 添加一个新的项到列表中
///
void Add(T item);
///
/// 从列表中清除所有的项
///
void Clear();
///
/// 返回列表是否包含特定的项
///
bool Contains(T item);
///
/// 从列表复制到数组中
///
void CopyTo(T[] array, int arrayIndex);
///
/// 移除特定对象的第一个匹配项
///
bool Remove(T item);
}
}
List
using System;
using System.Collections.Generic;
using System.Collections;
namespace StructScript
{
public class ListScript : IList
{
//默认容量为4
private const int DEFAULT_CAPACITY = 4;
//数组最大长度
private const int MAX_ARRAY_LENGTH = 0X7FEFFFFF;
private T[] mItems;
//列表的总长度
private int mSize;
//无论创建多少个对象,静态变量共享一个副本
private static readonly T[] mEmptyList = new T[0];
public ListScript()
{
mItems = mEmptyList;
}
public ListScript(int capacity)
{
if (capacity < 0 && capacity > MAX_ARRAY_LENGTH)
{
throw new IndexOutOfRangeException();
}
if (capacity == 0)
{
mItems = mEmptyList;
}
else
{
mItems = new T[capacity];
}
}
public ListScript(IEnumerable collection)
{
if (collection == null)
{
throw new ArgumentNullException();
}
ICollection list = collection as ICollection;
if (list != null)
{
int count = list.Count;
//初始化时,传入一个collection
if (count == 0)
{
mItems = mEmptyList;
}
else
{
//创建一个数组,把collection的数据复制到mItems中,
//同时mSize加上count的数量
mItems = new T[count];
list.CopyTo(mItems, 0);
mSize += count;
}
}
else
{
throw new InvalidCastException();
}
}
///
/// 得到或设置列表的容量
///
public int Capacity
{
get
{
return mItems.Length;
}
set
{
if (value < mSize)
{
throw new IndexOutOfRangeException();
}
if (value != mItems.Length)
{
if (value > 0)
{
T[] newItems = new T[value];
if (mSize > 0)
{
Copy(ref mItems, 0, ref newItems, 0, mSize);
}
mItems = newItems;
}
else
{
mItems = mEmptyList;
}
}
}
}
///
/// 返回列表中项的总数
///
public int Count
{
get
{
return mSize;
}
}
public object SyncRoot
{
get
{
throw new NotImplementedException();
}
}
public bool IsSynchronized
{
get
{
throw new NotImplementedException();
}
}
//列表的索引器
public T this[int index]
{
get
{
if (index >= mSize || index < 0)
{
throw new IndexOutOfRangeException();
}
return mItems[index];
}
set
{
if (index >= mSize || index < 0)
{
throw new IndexOutOfRangeException();
}
mItems[index] = value;
}
}
///
/// 添加一个新的项到列表中
///
///
public void Add(T value)
{
//如果数组满了,先扩容
if (mSize == mItems.Length)
{
EnsureCapacity(mSize + 1);
}
mItems[mSize] = value;
mSize++;
}
///
/// 保证列表容量
///
///
private void EnsureCapacity(int size)
{
int length = mItems.Length;
if (length < size)
{
int newCapacity = length == 0 ? DEFAULT_CAPACITY : length * 2;
//如果大于数组最大长度,设置为数组最大长度
if (newCapacity > MAX_ARRAY_LENGTH)
{
newCapacity = MAX_ARRAY_LENGTH;
}
//如果在AddRange新增数据时,length * 2也不能满足所需的容量,设置为size
if (newCapacity < size)
{
newCapacity = size;
}
Capacity = newCapacity;
}
}
///
/// 返回列表是否包含特定的项
///
///
///
public bool Contains(T value)
{
int index;
return Contain(value, out index);
}
///
/// 从列表中清除所有的项
///
public void Clear()
{
if (mSize > 0)
{
//Array.Clear(mItems, 0, mSize);
for (int i = 0; i < mSize; i++)
{
mItems[i] = default(T);
}
mSize = 0;
}
}
private bool Contain(T value, out int index)
{
index = 0;
if (value != null)
{
//用于比较泛型参数指定的类型
//EqualityComparer compare = EqualityComparer.Default;
for (int i = 0; i < mSize; i++)
{
//if (compare.Equals(mItems[i], value))
if (Equal(mItems[i], value))
{
index = i;
return true;
}
}
}
return false;
}
private bool ContainReverse(T value, out int index)
{
index = 0;
if (value != null)
{
//用于比较泛型参数指定的类型
//EqualityComparer compare = EqualityComparer.Default;
for (int i = mSize - 1; i > 0; i--)
{
//if (compare.Equals(mItems[i], value))
if (Equal(mItems[i], value))
{
index = i;
return true;
}
}
}
return false;
}
private bool Equal(Object obj1, Object obj2)
{
return obj1 == obj2;
}
///
/// 搜索指定的对象,并返回整个 List 中第一个匹配项的索引
/// 如果项不在列表中,返回-1
///
///
///
public int IndexOf(T value)
{
//实际上调用的是下面的语句,不过是在保护级别中,不能直接调用
//EqualityComparer.Default.IndexOf(T[] array, T value, startIndex, count);
int index;
if (Contain(value, out index))
{
return index;
}
return -1;
}
///
/// 搜索指定对象并返回整个 List 中最后一个匹配项的索引
/// 如果项不在列表中,返回-1
///
///
///
public int LastIndexOf(T value)
{
int index;
if (ContainReverse(value, out index))
{
return index;
}
return -1;
}
///
/// 插入一个值到列表的一个固定位置
///
///
///
public void Insert(int index, T value)
{
if (index >= mSize || index < 0)
{
throw new IndexOutOfRangeException();
}
//如果数组满了,先扩容
if (mSize == mItems.Length)
{
EnsureCapacity(mSize + 1);
}
//当插入的index小于mSize,并且数组没有满,
//这时就可以把插入位置之后的数据复制并依次粘贴到当前数组的下一个位置上
Copy(ref mItems, index, ref mItems, index + 1, mSize - index);
mItems[index] = value;
mSize++;
}
///
/// 从 List 中移除特定对象的第一个匹配项
///
///
public bool Remove(T value)
{
int index = IndexOf(value);
if (index >= 0)
{
RemoveAt(index);
return true;
}
return false;
}
///
/// 移除索引位置的项
///
///
public void RemoveAt(int index)
{
if (index >= mSize || index < 0)
{
throw new IndexOutOfRangeException();
}
mSize--;
//把索引位置之后的数据复制并覆盖从索引位置开始到结尾的数据
Copy(ref mItems, index + 1, ref mItems, index, mSize - index);
//default此关键字对于引用类型会返回空,对于数值类型会返回零, string类型返回""
//default用来获取一个类型的默认值
mItems[mSize] = default(T);
}
///
/// 从目标数组的指定索引处开始,将整个 List 复制到兼容的一维数组
///
///
///
public void CopyTo(T[] array, int arrayIndex)
{
//Array.Copy(mItems, 0, array, arrayIndex, mSize);
Copy(ref mItems, 0, ref array, arrayIndex, mSize);
}
///
/// 将 List 的元素复制到新数组中
///
///
public T[] ToArray()
{
T[] array = new T[mSize];
Copy(ref mItems, 0, ref array, 0, mSize);
return array;
}
///
/// 使用默认比较器对整个 List 中的元素进行排序
///
public void Sort()
{
ArraySortHelper.Default.Sort(mItems, 0, mSize, null);
}
public void Sort(IComparer comparer)
{
ArraySortHelper.Default.Sort(mItems, 0, mSize, comparer);
}
public void Sort(Comparison comparsion)
{
if (comparsion == null)
{
throw new ArgumentNullException();
}
if (mSize > 0)
{
IComparer comparer = new FunctorComparer(comparsion);
Sort(comparer);
}
}
///
/// 从给定的索引开始复制数组中的一系列元素,将它们粘贴到另一数组中(从给定的目的开始复制的索引开始)。
///
/// 包含要复制的数据
/// 开始复制的索引
/// 接收数据
/// 目的开始复制的索引
/// 要复制的元素数目
private void Copy(ref T[] sourceArray, int sourceIndex, ref T[] destinationArray, int destinationIndex, int length)
{
if (sourceIndex >= destinationIndex)
{
while (length > 0)
{
destinationArray[destinationIndex] = sourceArray[sourceIndex];
length--;
sourceIndex++;
destinationIndex++;
}
}
else
{
int lastIndex = sourceIndex + length - 1;
int otherLastIndex = destinationIndex + length - 1;
while (length > 0)
{
destinationArray[otherLastIndex] = sourceArray[lastIndex];
length--;
lastIndex--;
otherLastIndex--;
}
}
}
///
/// 将整个 List 中元素的顺序反转
///
public void Reverse()
{
int i = 0;
int j = mSize - 1;
while (i < j)
{
T temp = mItems[i];
mItems[i] = mItems[j];
mItems[j] = temp;
i++;
j--;
}
}
///
/// 将指定集合的元素添加到 List 的末尾
///
///
public void AddRange(IEnumerable collection)
{
if (collection == null)
{
throw new ArgumentNullException();
}
ICollection list = collection as ICollection;
if (list != null)
{
int count = list.Count;
//想要增加新的数据,先扩容
EnsureCapacity(mSize + count);
//这里有一种情况是,AddRange的是当前的list
if (this == list)
{
//直接复制当前的数组到结尾,不需要new一个新的数组转存数据
Copy(ref mItems, 0, ref mItems, mSize, mSize);
}
else
{
T[] addRangeItems = new T[count];
//复制新增的collection数组到addRangeItems中
list.CopyTo(addRangeItems, 0);
//从mItems数组的mSize索引开始,复制数组addRangeItems到mItems中
addRangeItems.CopyTo(mItems, mSize);
}
mSize += count;
}
else
{
throw new InvalidCastException();
}
}
public IEnumerator GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(this);
}
public struct Enumerator : IEnumerator
{
private ListScript list;
private int index;
private T current;
public Enumerator(ListScript list)
{
this.list = list;
index = 0;
current = default(T);
}
public T Current
{
get
{
if (index <= 0 || index > list.Count)
{
throw new IndexOutOfRangeException();
}
return current;
}
}
object IEnumerator.Current
{
get
{
if (index <= 0 || index > list.Count)
{
throw new IndexOutOfRangeException();
}
return current;
}
}
public void Dispose()
{
}
public bool MoveNext()
{
if (index >= 0 && index < list.Count)
{
current = list[index];
index++;
return true;
}
return false;
}
public void Reset()
{
index = 0;
current = default(T);
}
}
}
public sealed class FunctorComparer : IComparer
{
Comparison comparison;
public FunctorComparer(Comparison comparison)
{
this.comparison = comparison;
}
public int Compare(T x, T y)
{
return comparison(x, y);
}
}
}
C#内部排序实现:
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace StructScript
{
public interface IArraySortHelper
{
void Sort(TKey[] keys, int index, int length, IComparer comparer);
int BinarySearch(TKey[] keys, int index, int length, TKey value, IComparer comparer);
}
public class ArraySortHelper : IArraySortHelper
{
static volatile IArraySortHelper defaultArraySortHelper;
public static IArraySortHelper Default
{
get
{
IArraySortHelper sorter = defaultArraySortHelper;
if (sorter == null)
sorter = CreateArraySortHelper();
return sorter;
}
}
[System.Security.SecuritySafeCritical] // auto-generated
private static IArraySortHelper CreateArraySortHelper()
{
//if (typeof(IComparable).IsAssignableFrom(typeof(T)))
//{
// defaultArraySortHelper = (IArraySortHelper)RuntimeTypeHandle.Allocate(typeof(GenericArraySortHelper).TypeHandle.Instantiate(new Type[] { typeof(T) }));
//}
//else
{
defaultArraySortHelper = new ArraySortHelper();
}
return defaultArraySortHelper;
}
#region IArraySortHelper Members
public void Sort(T[] keys, int index, int length, IComparer comparer)
{
Contract.Assert(keys != null, "Check the arguments in the caller!");
Contract.Assert(index >= 0 && length >= 0 && (keys.Length - index >= length), "Check the arguments in the caller!");
// Add a try block here to detect IComparers (or their
// underlying IComparables, etc) that are bogus.
try
{
if (comparer == null)
{
comparer = Comparer.Default;
}
#if FEATURE_CORECLR
// Since QuickSort and IntrospectiveSort produce different sorting sequence for equal keys the upgrade
// to IntrospectiveSort was quirked. However since the phone builds always shipped with the new sort aka
// IntrospectiveSort and we would want to continue using this sort moving forward CoreCLR always uses the new sort.
IntrospectiveSort(keys, index, length, comparer);
#else
//if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
//{
IntrospectiveSort(keys, index, length, comparer);
//}
//else
//{
// DepthLimitedQuickSort(keys, index, length + index - 1, comparer, 32);
//}
#endif
}
catch (IndexOutOfRangeException)
{
//IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer);
}
catch (Exception e)
{
throw new InvalidOperationException();//Environment.GetResourceString("InvalidOperation_IComparerFailed"), e);
}
}
public int BinarySearch(T[] array, int index, int length, T value, IComparer comparer)
{
try
{
if (comparer == null)
{
comparer = Comparer.Default;
}
return InternalBinarySearch(array, index, length, value, comparer);
}
catch (Exception e)
{
throw new InvalidOperationException();//Environment.GetResourceString("InvalidOperation_IComparerFailed"), e);
}
}
#endregion
internal static int InternalBinarySearch(T[] array, int index, int length, T value, IComparer comparer)
{
Contract.Requires(array != null, "Check the arguments in the caller!");
Contract.Requires(index >= 0 && length >= 0 && (array.Length - index >= length), "Check the arguments in the caller!");
int lo = index;
int hi = index + length - 1;
while (lo <= hi)
{
int i = lo + ((hi - lo) >> 1);
int order = comparer.Compare(array[i], value);
if (order == 0) return i;
if (order < 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}
return ~lo;
}
private static void SwapIfGreater(T[] keys, IComparer comparer, int a, int b)
{
if (a != b)
{
if (comparer.Compare(keys[a], keys[b]) > 0)
{
T key = keys[a];
keys[a] = keys[b];
keys[b] = key;
}
}
}
private static void Swap(T[] a, int i, int j)
{
if (i != j)
{
T t = a[i];
a[i] = a[j];
a[j] = t;
}
}
internal static void DepthLimitedQuickSort(T[] keys, int left, int right, IComparer comparer, int depthLimit)
{
do
{
if (depthLimit == 0)
{
Heapsort(keys, left, right, comparer);
return;
}
int i = left;
int j = right;
// pre-sort the low, middle (pivot), and high values in place.
// this improves performance in the face of already sorted data, or
// data that is made up of multiple sorted runs appended together.
// >> 运算符相当于除,5 >> 1,相当于5/2,等于2
int middle = i + ((j - i) >> 1);
SwapIfGreater(keys, comparer, i, middle); // swap the low with the mid point
SwapIfGreater(keys, comparer, i, j); // swap the low with the high
SwapIfGreater(keys, comparer, middle, j); // swap the middle with the high
T x = keys[middle];
do
{
while (comparer.Compare(keys[i], x) < 0) i++;
while (comparer.Compare(x, keys[j]) < 0) j--;
Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) Sort failed - Is your IComparer bogus?");
if (i > j) break;
if (i < j)
{
T key = keys[i];
keys[i] = keys[j];
keys[j] = key;
}
i++;
j--;
} while (i <= j);
// The next iteration of the while loop is to "recursively" sort the larger half of the array and the
// following calls recrusively sort the smaller half. So we subtrack one from depthLimit here so
// both sorts see the new value.
depthLimit--;
if (j - left <= right - i)
{
if (left < j) DepthLimitedQuickSort(keys, left, j, comparer, depthLimit);
left = i;
}
else
{
if (i < right) DepthLimitedQuickSort(keys, i, right, comparer, depthLimit);
right = j;
}
} while (left < right);
}
internal static void IntrospectiveSort(T[] keys, int left, int length, IComparer comparer)
{
Contract.Requires(keys != null);
Contract.Requires(comparer != null);
Contract.Requires(left >= 0);
Contract.Requires(length >= 0);
Contract.Requires(length <= keys.Length);
Contract.Requires(length + left <= keys.Length);
if (length < 2)
return;
IntroSort(keys, left, length + left - 1, 2 * FloorLog2(keys.Length), comparer);
}
internal static int FloorLog2(int n)
{
int num = 0;
while (n >= 1)
{
++num;
n /= 2;
}
return num;
}
private static void IntroSort(T[] keys, int lo, int hi, int depthLimit, IComparer comparer)
{
Contract.Requires(keys != null);
Contract.Requires(comparer != null);
Contract.Requires(lo >= 0);
Contract.Requires(hi < keys.Length);
while (hi > lo)
{
int partitionSize = hi - lo + 1;
if (partitionSize <= 16)
{
if (partitionSize == 1)
{
return;
}
if (partitionSize == 2)
{
SwapIfGreater(keys, comparer, lo, hi);
return;
}
if (partitionSize == 3)
{
SwapIfGreater(keys, comparer, lo, hi - 1);
SwapIfGreater(keys, comparer, lo, hi);
SwapIfGreater(keys, comparer, hi - 1, hi);
return;
}
InsertionSort(keys, lo, hi, comparer);
return;
}
if (depthLimit == 0)
{
Heapsort(keys, lo, hi, comparer);
return;
}
depthLimit--;
int p = PickPivotAndPartition(keys, lo, hi, comparer);
// Note we've already partitioned around the pivot and do not have to move the pivot again.
IntroSort(keys, p + 1, hi, depthLimit, comparer);
hi = p - 1;
}
}
private static int PickPivotAndPartition(T[] keys, int lo, int hi, IComparer comparer)
{
Contract.Requires(keys != null);
Contract.Requires(comparer != null);
Contract.Requires(lo >= 0);
Contract.Requires(hi > lo);
Contract.Requires(hi < keys.Length);
Contract.Ensures(Contract.Result() >= lo && Contract.Result() <= hi);
// Compute median-of-three. But also partition them, since we've done the comparison.
int middle = lo + ((hi - lo) / 2);
// Sort lo, mid and hi appropriately, then pick mid as the pivot.
SwapIfGreater(keys, comparer, lo, middle); // swap the low with the mid point
SwapIfGreater(keys, comparer, lo, hi); // swap the low with the high
SwapIfGreater(keys, comparer, middle, hi); // swap the middle with the high
T pivot = keys[middle];
Swap(keys, middle, hi - 1);
int left = lo, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below.
while (left < right)
{
while (comparer.Compare(keys[++left], pivot) < 0) ;
while (comparer.Compare(pivot, keys[--right]) < 0) ;
if (left >= right)
break;
Swap(keys, left, right);
}
// Put pivot in the right location.
Swap(keys, left, (hi - 1));
return left;
}
private static void Heapsort(T[] keys, int lo, int hi, IComparer comparer)
{
Contract.Requires(keys != null);
Contract.Requires(comparer != null);
Contract.Requires(lo >= 0);
Contract.Requires(hi > lo);
Contract.Requires(hi < keys.Length);
int n = hi - lo + 1;
for (int i = n / 2; i >= 1; i = i - 1)
{
DownHeap(keys, i, n, lo, comparer);
}
for (int i = n; i > 1; i = i - 1)
{
Swap(keys, lo, lo + i - 1);
DownHeap(keys, 1, i - 1, lo, comparer);
}
}
private static void DownHeap(T[] keys, int i, int n, int lo, IComparer comparer)
{
Contract.Requires(keys != null);
Contract.Requires(comparer != null);
Contract.Requires(lo >= 0);
Contract.Requires(lo < keys.Length);
T d = keys[lo + i - 1];
int child;
while (i <= n / 2)
{
child = 2 * i;
if (child < n && comparer.Compare(keys[lo + child - 1], keys[lo + child]) < 0)
{
child++;
}
if (!(comparer.Compare(d, keys[lo + child - 1]) < 0))
break;
keys[lo + i - 1] = keys[lo + child - 1];
i = child;
}
keys[lo + i - 1] = d;
}
private static void InsertionSort(T[] keys, int lo, int hi, IComparer comparer)
{
Contract.Requires(keys != null);
Contract.Requires(lo >= 0);
Contract.Requires(hi >= lo);
Contract.Requires(hi <= keys.Length);
int i, j;
T t;
for (i = lo; i < hi; i++)
{
j = i;
t = keys[i + 1];
while (j >= lo && comparer.Compare(t, keys[j]) < 0)
{
keys[j + 1] = keys[j];
j--;
}
keys[j + 1] = t;
}
}
}
}
最后附上,测试用例:
using System;
namespace StructScript
{
public class Fruit
{
public string name;
public int num;
}
class TestList
{
static void Main(string[] args)
{
int[] arr = new int[] { 123, 121, 111, 123, 123 };
ListScript list__ = new ListScript(arr);
ShowData(list__);
ListScript list = new ListScript();
list.Add(1);
list.Add(2);
//需要实现IEnumerable接口
ListScript list1 = new ListScript() { 123, 121, 111, 123, 123 };
list1.AddRange(list);
ShowData(list1);
Console.WriteLine("第一个匹配项的索引 " + list1.IndexOf(123));
Console.WriteLine("最后一个匹配项的索引 " + list1.LastIndexOf(123));
Console.WriteLine("返回列表是否包含特定的项" + list1.Contains(123));
list1.Insert(2, 12);
Console.WriteLine("在索引2的位置插入了12");
ShowData(list1);
Console.WriteLine("删除索引3位置的数据");
list1.RemoveAt(3);
ShowData(list1);
Console.WriteLine("移除特定对象的第一个匹配项");
list1.Remove(123);
ShowData(list1);
//Console.WriteLine("对列表进行排序");
//list1.Sort();
//ShowData(list1);
Console.WriteLine("反转列表的元素 ");
list1.Reverse();
ShowData(list1);
ListScript list2 = new ListScript();
for (int i = 0; i < 5; i++)
{
Fruit apple = new Fruit();
apple.name = "苹果";
apple.num = i + 1;
list2.Add(apple);
}
list2.Sort(SortDescend);
//foreach语句中,第一次遇到Fruit对象时,调用GetEnumerator(),
//当每次执行到in关键字,调用MoveNext()方法,每次读取数据则调用Current属性。
foreach (Fruit item in list2)
{
Console.WriteLine(item.name + " " + item.num);
}
list2.Clear();
foreach (Fruit item in list2)
{
Console.WriteLine(item.name + " " + item.num);
}
Console.ReadLine();
}
public static void ShowData(ListScript list1)
{
int count = list1.Count;
for (int i = 0; i < count; i++)
{
Console.Write(list1[i] + " ");
}
Console.WriteLine();
}
//指示 x 与 y 的相对值,如下表所示。值含义小于 0x 小于 y。0x 等于 y。大于 0x 大于 y。
public static int SortDescend(Fruit fruit1, Fruit fruit2)
{
if (fruit1 == null && fruit2 == null)
{
return 0;
}
else if (fruit1 != null && fruit2 == null)
{
return 1;
}
else if (fruit1 == null && fruit2 != null)
{
return -1;
}
if (fruit1.num > fruit2.num)
{
return -1;
}
else if (fruit1.num < fruit2.num)
{
return 1;
}
return 1;
}
}
}
总结:
在实现List
这里,犯一个很大的错误,所以才出现了这样的结果,请看下回分解:C#数据结构-Array.Copy和Buffer.BlockCopy详解