File: System\Data\DataColumnCollection.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="DataColumnCollection.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
// <owner current="false" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
namespace System.Data {
    using System;
    using System.Xml;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data.Common;
    using System.Diagnostics;
 
    /// <devdoc>
    /// <para>Represents a collection of <see cref='System.Data.DataColumn'/>
    /// objects for a <see cref='System.Data.DataTable'/>.</para>
    /// </devdoc>
    [
    DefaultEvent("CollectionChanged"),
    Editor("Microsoft.VSDesigner.Data.Design.ColumnsCollectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
    ]
    public sealed class DataColumnCollection : InternalDataCollectionBase {
 
        private readonly DataTable table;
        private readonly ArrayList _list = new ArrayList();
        private int defaultNameIndex = 1;
        private DataColumn[] delayedAddRangeColumns;
 
        private readonly Dictionary<string, DataColumn> columnFromName;     // Links names to columns
        private CollectionChangeEventHandler onCollectionChangedDelegate;
        private CollectionChangeEventHandler onCollectionChangingDelegate;
 
        private CollectionChangeEventHandler onColumnPropertyChangedDelegate;
 
        private bool fInClear;
 
        private DataColumn[] columnsImplementingIChangeTracking = DataTable.zeroColumns;
        private int nColumnsImplementingIChangeTracking = 0;
        private int nColumnsImplementingIRevertibleChangeTracking = 0;
 
        /// <devdoc>
        /// DataColumnCollection constructor.  Used only by DataTable.
        /// </devdoc>
        internal DataColumnCollection(DataTable table) {
            this.table = table;
            columnFromName = new Dictionary<string, DataColumn>();
        }
 
        /// <devdoc>
        ///    <para>Gets the list of the collection items.</para>
        /// </devdoc>
        protected override ArrayList List {
            get {
                return _list;
            }
        }
 
        internal DataColumn[] ColumnsImplementingIChangeTracking {
            get {
                return columnsImplementingIChangeTracking;
            }
        }
        internal int ColumnsImplementingIChangeTrackingCount{
            get {
                return nColumnsImplementingIChangeTracking;
            }
        }
        internal int ColumnsImplementingIRevertibleChangeTrackingCount {
            get {
                return nColumnsImplementingIRevertibleChangeTracking;
            }
        }
        /// <devdoc>
        ///    <para>
        ///       Gets the <see cref='System.Data.DataColumn'/>
        ///       from the collection at the specified index.
        ///    </para>
        /// </devdoc>
        public DataColumn this[int index] {
            get {
                try { // Perf: use the readonly _list field directly and let ArrayList check the range
                    return (DataColumn)_list[index];
                }
                catch(ArgumentOutOfRangeException) {
                    throw ExceptionBuilder.ColumnOutOfRange(index);
                }
            }
        }
 
        /// <devdoc>
        /// <para>Gets the <see cref='System.Data.DataColumn'/> from the collection with the specified name.</para>
        /// </devdoc>
        public DataColumn this[string name] {
            get {
                if (null == name) {
                    throw ExceptionBuilder.ArgumentNull("name");
                }
                DataColumn column;
                if ((!columnFromName.TryGetValue(name, out column)) || (column == null)) {
                    // Case-Insensitive compares
                    int index = IndexOfCaseInsensitive(name);
                    if (0 <= index) {
                        column = (DataColumn)_list[index];
                    }
                    else if (-2 == index) {
                        throw ExceptionBuilder.CaseInsensitiveNameConflict(name);
                    }
                }
                return column;
            }
        }
 
        internal DataColumn this[string name, string ns] {
            get {
                DataColumn column;
                if ((columnFromName.TryGetValue(name, out column)) && (column != null) && (column.Namespace == ns)) {
                    return column;
                }
 
                return null;
            }
        }
 
        internal void EnsureAdditionalCapacity(int capacity) {
            if (_list.Capacity < capacity + _list.Count) {
                _list.Capacity = capacity + _list.Count;
            }
        }
 
        /// <devdoc>
        /// <para>Adds the specified <see cref='System.Data.DataColumn'/>
        /// to the columns collection.</para>
        /// </devdoc>
        public void Add(DataColumn column) {
            AddAt(-1, column);
        }
 
        internal void AddAt(int index, DataColumn column) {
            if (column != null && column.ColumnMapping == MappingType.SimpleContent) {
                if (table.XmlText != null && table.XmlText != column)
                    throw ExceptionBuilder.CannotAddColumn3();
                if (table.ElementColumnCount > 0)
                    throw ExceptionBuilder.CannotAddColumn4(column.ColumnName);
                OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Add, column));
                BaseAdd(column);
                if (index != -1)
                    ArrayAdd(index, column);
                else
                    ArrayAdd(column);
 
                table.XmlText = column;
            }
            else {
                OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Add, column));
                BaseAdd(column);
                if (index != -1)
                    ArrayAdd(index, column);
                else
                    ArrayAdd(column);
                // if the column is an element increase the internal dataTable counter
                if (column.ColumnMapping == MappingType.Element)
                    table.ElementColumnCount ++;
            }
            if (!table.fInitInProgress && column != null && column.Computed) {
                column.Expression = column.Expression;
            }
            OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, column));
        }
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public void AddRange(DataColumn[] columns) {
            if (table.fInitInProgress) {
                delayedAddRangeColumns = columns;
                return;
            }
 
            if (columns != null) {
                foreach(DataColumn column in columns) {
                    if (column != null) {
                        Add(column);
                    }
                }
            }
        }
 
        /// <devdoc>
        /// <para>Creates and adds a <see cref='System.Data.DataColumn'/>
        /// with
        /// the specified name, type, and compute expression to the columns collection.</para>
        /// </devdoc>
        public DataColumn Add(string columnName, Type type, string expression) {
            DataColumn column = new DataColumn(columnName, type, expression);
            Add(column);
            return column;
        }
 
        /// <devdoc>
        /// <para>Creates and adds a <see cref='System.Data.DataColumn'/>
        /// with the
        /// specified name and type to the columns collection.</para>
        /// </devdoc>
        public DataColumn Add(string columnName, Type type) {
            DataColumn column = new DataColumn(columnName, type);
            Add(column);
            return column;
        }
 
        /// <devdoc>
        /// <para>Creates and adds a <see cref='System.Data.DataColumn'/>
        /// with the specified name to the columns collection.</para>
        /// </devdoc>
        public DataColumn Add(string columnName) {
            DataColumn column = new DataColumn(columnName);
            Add(column);
            return column;
        }
 
        /// <devdoc>
        /// <para>Creates and adds a <see cref='System.Data.DataColumn'/> to a columns collection.</para>
        /// </devdoc>
        public DataColumn Add() {
            DataColumn column = new DataColumn();
            Add(column);
            return column;
        }
 
 
        /// <devdoc>
        ///    <para>Occurs when the columns collection changes, either by adding or removing a column.</para>
        /// </devdoc>
        [ResDescriptionAttribute(Res.collectionChangedEventDescr)]
        public event CollectionChangeEventHandler CollectionChanged {
            add {
                onCollectionChangedDelegate += value;
            }
            remove {
                onCollectionChangedDelegate -= value;
            }
        }
 
        internal event CollectionChangeEventHandler CollectionChanging {
            add {
                onCollectionChangingDelegate += value;
            }
            remove {
                onCollectionChangingDelegate -= value;
            }
        }
 
        internal event CollectionChangeEventHandler ColumnPropertyChanged {
            add {
                onColumnPropertyChangedDelegate += value;
            }
            remove {
                onColumnPropertyChangedDelegate -= value;
            }
        }
 
        /// <devdoc>
        ///  Adds the column to the columns array.
        /// </devdoc>
        private void ArrayAdd(DataColumn column) {
            _list.Add(column);
            column.SetOrdinalInternal(_list.Count - 1);
            CheckIChangeTracking(column);
        }
 
        private void ArrayAdd(int index, DataColumn column) {
            _list.Insert(index, column);
            CheckIChangeTracking(column);
        }
 
        private void ArrayRemove(DataColumn column) {
            column.SetOrdinalInternal(-1);
            _list.Remove(column);
 
            int count = _list.Count;
            for (int i =0; i < count; i++) {
                ((DataColumn) _list[i]).SetOrdinalInternal(i);
            }
            if (column.ImplementsIChangeTracking) {
                RemoveColumnsImplementingIChangeTrackingList(column);
            }
        }
 
        /// <devdoc>
        /// Creates a new default name.
        /// </devdoc>
        internal string AssignName() {
            string newName = MakeName(defaultNameIndex++);
 
            while (columnFromName.ContainsKey(newName)) {
                newName = MakeName(defaultNameIndex++);
            }
 
            return newName;
        }
 
        /// <devdoc>
        /// Does verification on the column and it's name, and points the column at the dataSet that owns this collection.
        /// An ArgumentNullException is thrown if this column is null.  An ArgumentException is thrown if this column
        /// already belongs to this collection, belongs to another collection.
        /// A DuplicateNameException is thrown if this collection already has a column with the same
        /// name (case insensitive).
        /// </devdoc>
        private void BaseAdd(DataColumn column) {
            if (column == null)
                throw ExceptionBuilder.ArgumentNull("column");
            if (column.table == table)
                throw ExceptionBuilder.CannotAddColumn1(column.ColumnName);
            if (column.table != null)
                throw ExceptionBuilder.CannotAddColumn2(column.ColumnName);
            
            if (column.ColumnName.Length == 0) {
                column.ColumnName = AssignName();
            }
            RegisterColumnName(column.ColumnName, column);
            try {
                column.SetTable(table);
                if (!table.fInitInProgress && column.Computed) {
                    if (column.DataExpression.DependsOn(column)) {
                        throw ExceptionBuilder.ExpressionCircular();
                    }
                }
 
                if (0 < table.RecordCapacity) {
                    // adding a column to table with existing rows
                    column.SetCapacity(table.RecordCapacity);
                }
 
                // fill column with default value.
                for (int record = 0; record < table.RecordCapacity; record++) {
                    column.InitializeRecord(record);
                }
 
                if (table.DataSet != null) {
                    column.OnSetDataSet();
                }
            }
            catch (Exception e) {
                // 
                if (ADP.IsCatchableOrSecurityExceptionType(e)) {
                    UnregisterName(column.ColumnName);
                }
                throw;
            }
        }
 
        /// <devdoc>
        /// BaseGroupSwitch will intelligently remove and add tables from the collection.
        /// </devdoc>
        private void BaseGroupSwitch(DataColumn[] oldArray, int oldLength, DataColumn[] newArray, int newLength) {
            // We're doing a smart diff of oldArray and newArray to find out what
            // should be removed.  We'll pass through oldArray and see if it exists
            // in newArray, and if not, do remove work.  newBase is an opt. in case
            // the arrays have similar prefixes.
            int newBase = 0;
            for (int oldCur = 0; oldCur < oldLength; oldCur++) {
                bool found = false;
                for (int newCur = newBase; newCur < newLength; newCur++) {
                    if (oldArray[oldCur] == newArray[newCur]) {
                        if (newBase == newCur) {
                            newBase++;
                        }
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    // This means it's in oldArray and not newArray.  Remove it.
                    if (oldArray[oldCur].Table == table) {
                        BaseRemove(oldArray[oldCur]);
                        _list.Remove(oldArray[oldCur]);
                        oldArray[oldCur].SetOrdinalInternal(-1);
                    }
                }
            }
 
            // Now, let's pass through news and those that don't belong, add them.
            for (int newCur = 0; newCur < newLength; newCur++) {
                if (newArray[newCur].Table != table) {
                    BaseAdd(newArray[newCur]);
                    _list.Add(newArray[newCur]);
                }
                newArray[newCur].SetOrdinalInternal(newCur);
            }
        }
 
        /// <devdoc>
        /// Does verification on the column and it's name, and clears the column's dataSet pointer.
        /// An ArgumentNullException is thrown if this column is null.  An ArgumentException is thrown
        /// if this column doesn't belong to this collection or if this column is part of a relationship.
        /// An ArgumentException is thrown if another column's compute expression depends on this column.
        /// </devdoc>
        private void BaseRemove(DataColumn column) {
            if (CanRemove(column, true)) {
 
                // remove
                if (column.errors > 0) {
                    for (int i = 0; i < table.Rows.Count; i++) {
                        table.Rows[i].ClearError(column);
                    }
                }
                UnregisterName(column.ColumnName);
                column.SetTable(null);
            }
        }
 
        /// <devdoc>
        ///    <para>Checks
        ///       if
        ///       a given column can be removed from the collection.</para>
        /// </devdoc>
        public bool CanRemove(DataColumn column) {
            return CanRemove(column, false);
        }
 
        internal bool CanRemove(DataColumn column, bool fThrowException) {
            if (column == null) {
                if (!fThrowException)
                    return false;
                else
                throw ExceptionBuilder.ArgumentNull("column");
            }
            if (column.table != table) {
                if (!fThrowException)
                    return false;
                else
                throw ExceptionBuilder.CannotRemoveColumn();
            }
 
            // allow subclasses to complain first.
            table.OnRemoveColumnInternal(column);
 
            // We need to make sure the column is not involved in any Relations or Constriants
            if (table.primaryKey != null && table.primaryKey.Key.ContainsColumn(column)) {
                if (!fThrowException)
                    return false;
                else
                throw ExceptionBuilder.CannotRemovePrimaryKey();
            }
            for (int i = 0; i < table.ParentRelations.Count; i++) {
                if (table.ParentRelations[i].ChildKey.ContainsColumn(column)) {
                    if (!fThrowException)
                        return false;
                    else
                    throw ExceptionBuilder.CannotRemoveChildKey(table.ParentRelations[i].RelationName);
                }
            }
            for (int i = 0; i < table.ChildRelations.Count; i++) {
                if (table.ChildRelations[i].ParentKey.ContainsColumn(column)) {
                    if (!fThrowException)
                        return false;
                    else
                    throw ExceptionBuilder.CannotRemoveChildKey(table.ChildRelations[i].RelationName);
                }
            }
            for (int i = 0; i < table.Constraints.Count; i++) {
                if (table.Constraints[i].ContainsColumn(column))
                    if (!fThrowException)
                        return false;
                    else
                    throw ExceptionBuilder.CannotRemoveConstraint(table.Constraints[i].ConstraintName, table.Constraints[i].Table.TableName);
            }
            if (table.DataSet != null) {
                for (ParentForeignKeyConstraintEnumerator en = new ParentForeignKeyConstraintEnumerator(table.DataSet, table); en.GetNext();) {
                    Constraint constraint = en.GetConstraint();
                    if (((ForeignKeyConstraint)constraint).ParentKey.ContainsColumn(column))
                        if (!fThrowException)
                            return false;
                        else
                            throw ExceptionBuilder.CannotRemoveConstraint(constraint.ConstraintName, constraint.Table.TableName);
                }
            }
 
            if (column.dependentColumns != null) {
                for (int i = 0; i < column.dependentColumns.Count; i++) {
                    DataColumn col = column.dependentColumns[i];
                    if (fInClear && (col.Table == table || col.Table == null))
                        continue;
                    if (col.Table == null)
                        continue;
                    Debug.Assert(col.Computed, "invalid (non an expression) column in the expression dependent columns");
                    DataExpression expr = col.DataExpression;
                    if ((expr!= null) && (expr.DependsOn(column))) {
                        if (!fThrowException)
                            return false;
                        else
                            throw ExceptionBuilder.CannotRemoveExpression(col.ColumnName, col.Expression);
                    }
                }
            }
 
            // SQLBU 429176: you can't remove a column participating in an index,
            // while index events are suspended else the indexes won't be properly maintained.
            // However, all the above checks should catch those participating columns.
            // except when a column is in a DataView RowFilter or Sort clause
            foreach (Index index in table.LiveIndexes) {
#if false
                if (!Object.ReferenceEquals(index, column.sortIndex)) {
                    foreach (IndexField field in index.IndexFields) {
                        if (Object.ReferenceEquals(field.Column, column)) {
                            if (fThrowException) {
                                throw ExceptionBuilder.CannotRemoveExpression("DataView", column.ColumnName);
                            }
                            return false;
                        }
                    }
                }
#endif
            }
 
            return true;
        }
 
        private void CheckIChangeTracking(DataColumn column) {
            if (column.ImplementsIRevertibleChangeTracking) {
                nColumnsImplementingIRevertibleChangeTracking++;
                nColumnsImplementingIChangeTracking++;
                AddColumnsImplementingIChangeTrackingList(column);
            }
            else if (column.ImplementsIChangeTracking) {
                nColumnsImplementingIChangeTracking++;
                AddColumnsImplementingIChangeTrackingList(column);
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Clears the collection of any columns.
        ///    </para>
        /// </devdoc>
        public void Clear() {
            int oldLength = _list.Count;
 
            DataColumn[] columns = new DataColumn[_list.Count];
            _list.CopyTo(columns, 0);
 
            OnCollectionChanging(RefreshEventArgs);
 
            if (table.fInitInProgress && delayedAddRangeColumns != null) {
                delayedAddRangeColumns = null;
            }
 
            try {
                // this will smartly add and remove the appropriate tables.
                fInClear = true;
                BaseGroupSwitch(columns, oldLength, null, 0);
                fInClear = false;
            }
            catch (Exception e) {
                // 
                if (ADP.IsCatchableOrSecurityExceptionType(e)) {
                    // something messed up: restore to old values and throw
                    fInClear = false;
                    BaseGroupSwitch(null, 0, columns, oldLength);
                    _list.Clear();
                    for (int i = 0; i < oldLength; i++)
                        _list.Add(columns[i]);
                }
                throw;
            }
            _list.Clear();
            table.ElementColumnCount  = 0;
            OnCollectionChanged(RefreshEventArgs);
        }
 
        /// <devdoc>
        ///    <para>Checks whether the collection contains a column with the specified name.</para>
        /// </devdoc>
        public bool Contains(string name) {
            DataColumn column;
            if ((columnFromName.TryGetValue(name, out column)) && (column != null)) {
                return true;
            }
 
            return (IndexOfCaseInsensitive(name) >= 0);
        }
 
        internal bool Contains(string name, bool caseSensitive) {
            DataColumn column;
            if ((columnFromName.TryGetValue(name, out column)) && (column != null)) {
                return true;
            }
 
            if (caseSensitive) { // above check did case sensitive check
                return false;
            }
            else {
                return (IndexOfCaseInsensitive(name) >= 0);
            }
        }
 
        public void CopyTo(DataColumn[] array, int index) {
            if (array==null)
                throw ExceptionBuilder.ArgumentNull("array");
            if (index < 0)
                throw ExceptionBuilder.ArgumentOutOfRange("index");
            if (array.Length - index < _list.Count)
                throw ExceptionBuilder.InvalidOffsetLength();
            for(int i = 0; i < _list.Count; ++i) {
                array[index + i] = (DataColumn)_list[i];
            }
        }
 
        /// <devdoc>
        ///    <para>
        ///       Returns the index of a specified <see cref='System.Data.DataColumn'/>.
        ///    </para>
        /// </devdoc>
        public int IndexOf(DataColumn column) {
            int columnCount = _list.Count;
            for (int i = 0; i < columnCount; ++i) {
                if (column == (DataColumn) _list[i]) {
                    return i;
                }
            }
            return -1;
        }
 
        /// <devdoc>
        ///    <para>Returns the index of
        ///       a column specified by name.</para>
        /// </devdoc>
        public int IndexOf(string columnName) {
 
            if ((null != columnName) && (0 < columnName.Length)) {
                int count = Count;
                DataColumn column;
                if ((columnFromName.TryGetValue(columnName, out column)) && (column != null)) {
                    for (int j = 0; j < count; j++)
                        if (column == _list[j]) {
                            return j;
                    }
                }
                else {
                    int res = IndexOfCaseInsensitive(columnName);
                    return (res < 0) ? -1 : res;
                }
            }
            return -1;
        }
 
        internal int IndexOfCaseInsensitive (string name) {
            int hashcode = table.GetSpecialHashCode(name);
            int cachedI = -1;
            DataColumn column = null;
            for (int i = 0; i < Count; i++) {
                column = (DataColumn) _list[i];
                if ( (hashcode == 0 || column._hashCode == 0 || column._hashCode == hashcode) &&
                   NamesEqual(column.ColumnName, name, false, table.Locale) != 0 ) {
                    if (cachedI == -1)
                        cachedI = i;
                    else
                        return -2;
                }
            }
            return cachedI;
        }
 
        internal void FinishInitCollection() {
            if (delayedAddRangeColumns != null) {
                foreach(DataColumn column in delayedAddRangeColumns) {
                    if (column != null) {
                        Add(column);
                    }
                }
 
                foreach(DataColumn column in delayedAddRangeColumns) {
                    if (column != null) {
                        column.FinishInitInProgress();
                    }
                }
 
                delayedAddRangeColumns = null;
            }
        }
 
        /// <devdoc>
        /// Makes a default name with the given index.  e.g. Column1, Column2, ... Columni
        /// </devdoc>
        private string MakeName(int index) {
            if (1 == index) {
                return "Column1";
            }
            return "Column" + index.ToString(System.Globalization.CultureInfo.InvariantCulture);
        }
 
        internal void MoveTo(DataColumn column, int newPosition) {
            if (0 > newPosition || newPosition > Count -1) {
                throw ExceptionBuilder.InvalidOrdinal("ordinal", newPosition);
            }
            if (column.ImplementsIChangeTracking) {
                RemoveColumnsImplementingIChangeTrackingList(column);
            }
            _list.Remove(column);
            _list.Insert(newPosition, column);
            int count = _list.Count;
            for (int i =0; i < count; i++) {
                ((DataColumn) _list[i]).SetOrdinalInternal(i);
            }
            CheckIChangeTracking(column);
            OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, column));
        }
 
        /// <devdoc>
        ///    <para>
        ///       Raises the <see cref='System.Data.DataColumnCollection.OnCollectionChanged'/> event.
        ///    </para>
        /// </devdoc>
        private void OnCollectionChanged(CollectionChangeEventArgs ccevent) {
            table.UpdatePropertyDescriptorCollectionCache();
 
            if ((null != ccevent) && !table.SchemaLoading && !table.fInitInProgress) {
                DataColumn column = (DataColumn)ccevent.Element;
            }
            if (onCollectionChangedDelegate != null) {
                onCollectionChangedDelegate(this, ccevent);
            }
        }
 
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        private void OnCollectionChanging(CollectionChangeEventArgs ccevent) {
            if (onCollectionChangingDelegate != null) {
                onCollectionChangingDelegate(this, ccevent);
            }
        }
 
        internal void OnColumnPropertyChanged(CollectionChangeEventArgs ccevent) {
            table.UpdatePropertyDescriptorCollectionCache();
            if (onColumnPropertyChangedDelegate != null) {
                onColumnPropertyChangedDelegate(this, ccevent);
            }
        }
 
        /// <devdoc>
        /// Registers this name as being used in the collection.  Will throw an ArgumentException
        /// if the name is already being used.  Called by Add, All property, and Column.ColumnName property.
        /// if the name is equivalent to the next default name to hand out, we increment our defaultNameIndex.
        /// NOTE: To add a child table, pass column as null
        /// </devdoc>
        internal void RegisterColumnName(string name, DataColumn column) {
            Debug.Assert (name != null);
 
            try {
                columnFromName.Add(name, column);
 
                if (null != column) {
                    column._hashCode = table.GetSpecialHashCode(name);
                }
            }
            catch (ArgumentException) { // Argument exception means that there is already an existing key
                if (columnFromName[name] != null) {
                    if (column != null) {
                        throw ExceptionBuilder.CannotAddDuplicate(name);
                    }
                    else {
                        throw ExceptionBuilder.CannotAddDuplicate3(name);
                    }
                }
                throw ExceptionBuilder.CannotAddDuplicate2(name);
            }
 
            // If we're adding a child table, then update defaultNameIndex to avoid colisions between the child table and auto-generated column names
            if ((column == null) && NamesEqual(name, MakeName(defaultNameIndex), true, table.Locale) != 0) {
                do {
                    defaultNameIndex++;
                } while (Contains(MakeName(defaultNameIndex)));
            }
        }
 
        internal bool CanRegisterName(string name) {
            Debug.Assert (name != null, "Must specify a name");
            return (!columnFromName.ContainsKey(name));
        }
 
        /// <devdoc>
        /// <para>Removes the specified <see cref='System.Data.DataColumn'/>
        /// from the collection.</para>
        /// </devdoc>
        public void Remove(DataColumn column) {
            OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Remove, column));
            BaseRemove(column);
            ArrayRemove(column);
            OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, column));
            // if the column is an element decrease the internal dataTable counter
            if (column.ColumnMapping == MappingType.Element)
                table.ElementColumnCount --;
        }
 
        /// <devdoc>
        ///    <para>Removes the
        ///       column at the specified index from the collection.</para>
        /// </devdoc>
        public void RemoveAt(int index) {
            DataColumn dc = this[index];
            if (dc == null)
                throw ExceptionBuilder.ColumnOutOfRange(index);
            Remove(dc);
        }
 
        /// <devdoc>
        ///    <para>Removes the
        ///       column with the specified name from the collection.</para>
        /// </devdoc>
        public void Remove(string name) {
            DataColumn dc = this[name];
            if (dc == null)
                throw ExceptionBuilder.ColumnNotInTheTable(name, table.TableName);
            Remove(dc);
        }
 
        /// <devdoc>
        /// Unregisters this name as no longer being used in the collection.  Called by Remove, All property, and
        /// Column.ColumnName property.  If the name is equivalent to the last proposed default name, we walk backwards
        /// to find the next proper default name to use.
        /// </devdoc>
        internal void UnregisterName(string name) {
            columnFromName.Remove(name);
 
            if (NamesEqual(name, MakeName(defaultNameIndex - 1), true, table.Locale) != 0) {
                do {
                    defaultNameIndex--;
                } while (defaultNameIndex > 1 &&
                         !Contains(MakeName(defaultNameIndex - 1)));
            }
        }
                
        private void AddColumnsImplementingIChangeTrackingList(DataColumn dataColumn) {
            DataColumn[] columns = columnsImplementingIChangeTracking;
            DataColumn[] tempColumns = new DataColumn[columns.Length +1];
            columns.CopyTo(tempColumns, 0);
            tempColumns[columns.Length] = dataColumn;
            columnsImplementingIChangeTracking = tempColumns;
        }
 
        private void RemoveColumnsImplementingIChangeTrackingList(DataColumn dataColumn) {
            DataColumn[] columns = columnsImplementingIChangeTracking;
            DataColumn[] tempColumns = new DataColumn[columns.Length - 1];
            for(int i = 0, j = 0; i < columns.Length; i++) {
                if (columns[i] != dataColumn) {
                    tempColumns[j++] = columns[i];
                }
            }
            columnsImplementingIChangeTracking = tempColumns;
        }
    }
}