2 // System.Data.DataTable.cs
5 // Franklin Wise <gracenote@earthlink.net>
6 // Christopher Podurgiel (cpodurgiel@msn.com)
7 // Daniel Morgan <danmorg@sc.rr.com>
8 // Rodrigo Moya <rodrigo@ximian.com>
9 // Tim Coleman (tim@timcoleman.com)
10 // Ville Palo <vi64pa@koti.soon.fi>
12 // (C) Chris Podurgiel
13 // (C) Ximian, Inc 2002
14 // Copyright (C) Tim Coleman, 2002
15 // Copyright (C) Daniel Morgan, 2002-2003
19 using System.Collections;
20 using System.ComponentModel;
21 using System.Globalization;
22 using System.Runtime.Serialization;
24 namespace System.Data {
27 [DefaultEvent ("RowChanging")]
28 [DefaultProperty ("TableName")]
29 [DesignTimeVisible (false)]
31 public class DataTable : MarshalByValueComponent, IListSource, ISupportInitialize, ISerializable
33 internal DataSet dataSet;
35 private bool _caseSensitive;
36 private DataColumnCollection _columnCollection;
37 private ConstraintCollection _constraintCollection;
38 private DataView _defaultView;
40 private string _displayExpression;
41 private PropertyCollection _extendedProperties;
42 private bool _hasErrors;
43 private CultureInfo _locale;
44 private int _minimumCapacity;
45 private string _nameSpace;
46 private DataRelationCollection _childRelations;
47 private DataRelationCollection _parentRelations;
48 private string _prefix;
49 private DataColumn[] _primaryKey;
50 private DataRowCollection _rows;
52 private string _tableName;
53 private bool _containsListCollection;
54 private string _encodedTableName;
57 // If CaseSensitive property is changed once it does not anymore follow owner DataSet's
58 // CaseSensitive property. So when you lost you virginity it's gone for ever
59 private bool _virginCaseSensitive = true;
62 /// Initializes a new instance of the DataTable class with no arguments.
68 _columnCollection = new DataColumnCollection(this);
69 _constraintCollection = new ConstraintCollection();
70 _extendedProperties = new PropertyCollection();
73 _caseSensitive = false; //default value
74 _displayExpression = null;
77 _rows = new DataRowCollection (this);
78 _locale = CultureInfo.CurrentCulture;
80 //LAMESPEC: spec says 25 impl does 50
81 _minimumCapacity = 50;
83 _childRelations = new DataRelationCollection.DataTableRelationCollection (this);
84 _parentRelations = new DataRelationCollection.DataTableRelationCollection (this);
87 _defaultView = new DataView(this);
91 /// Intitalizes a new instance of the DataTable class with the specified table name.
93 public DataTable (string tableName) : this ()
95 _tableName = tableName;
99 /// Initializes a new instance of the DataTable class with the SerializationInfo and the StreamingContext.
102 protected DataTable (SerializationInfo info, StreamingContext context)
106 // TODO: Add constructor logic here
111 /// Indicates whether string comparisons within the table are case-sensitive.
113 [DataSysDescription ("Indicates whether comparing strings within the table is case sensitive.")]
114 public bool CaseSensitive {
115 get { return _caseSensitive; }
117 _virginCaseSensitive = false;
118 _caseSensitive = value;
122 internal bool VirginCaseSensitive {
123 get { return _virginCaseSensitive; }
124 set { _virginCaseSensitive = value; }
127 internal void ChangedDataColumn (DataRow dr, DataColumn dc, object pv)
129 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs (dr, dc, pv);
133 internal void ChangingDataColumn (DataRow dr, DataColumn dc, object pv)
135 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs (dr, dc, pv);
136 OnColumnChanging (e);
139 internal void DeletedDataRow (DataRow dr, DataRowAction action)
141 DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action);
145 internal void ChangedDataRow (DataRow dr, DataRowAction action)
147 DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action);
152 /// Gets the collection of child relations for this DataTable.
155 [DataSysDescription ("Returns the child relations for this table.")]
156 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
157 public DataRelationCollection ChildRelations {
159 return _childRelations;
164 /// Gets the collection of columns that belong to this table.
166 [DataCategory ("Data")]
167 [DataSysDescription ("The collection that holds the columns for this table.")]
168 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
169 public DataColumnCollection Columns {
170 get { return _columnCollection; }
174 /// Gets the collection of constraints maintained by this table.
176 [DataCategory ("Data")]
177 [DataSysDescription ("The collection that holds the constraints for this table.")]
178 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
179 public ConstraintCollection Constraints {
180 get { return _constraintCollection; }
184 /// Gets the DataSet that this table belongs to.
187 [DataSysDescription ("Indicates the DataSet to which this table belongs.")]
188 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
189 public DataSet DataSet {
190 get { return dataSet; }
196 /// Gets a customized view of the table which may
197 /// include a filtered view, or a cursor position.
201 [DataSysDescription ("This is the default DataView for the table.")]
202 public DataView DefaultView {
203 get { return _defaultView; }
208 /// Gets or sets the expression that will return
209 /// a value used to represent this table in the user interface.
211 [DataCategory ("Data")]
212 [DataSysDescription ("The expression used to compute the data-bound value of this row.")]
214 public string DisplayExpression {
215 get { return "" + _displayExpression; }
216 set { _displayExpression = value; }
220 /// Gets the collection of customized user information.
223 [DataCategory ("Data")]
224 [DataSysDescription ("The collection that holds custom user information.")]
225 public PropertyCollection ExtendedProperties {
226 get { return _extendedProperties; }
230 /// Gets a value indicating whether there are errors in
231 /// any of the_rows in any of the tables of the DataSet to
232 /// which the table belongs.
235 [DataSysDescription ("Returns whether the table has errors.")]
236 public bool HasErrors {
237 get { return _hasErrors; }
241 /// Gets or sets the locale information used to
242 /// compare strings within the table.
244 [DataSysDescription ("Indicates a locale under which to compare strings within the table.")]
245 public CultureInfo Locale {
246 get { return _locale; }
247 set { _locale = value; }
251 /// Gets or sets the initial starting size for this table.
253 [DataCategory ("Data")]
254 [DataSysDescription ("Indicates an initial starting size for this table.")]
256 public int MinimumCapacity {
257 get { return _minimumCapacity; }
258 set { _minimumCapacity = value; }
262 /// Gets or sets the namespace for the XML represenation
263 /// of the data stored in the DataTable.
265 [DataCategory ("Data")]
266 [DataSysDescription ("Indicates the XML uri namespace for the elements contained in this table.")]
267 public string Namespace {
268 get { return "" + _nameSpace; }
269 set { _nameSpace = value; }
273 /// Gets the collection of parent relations for
277 [DataSysDescription ("Returns the parent relations for this table.")]
278 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
279 public DataRelationCollection ParentRelations {
281 return _parentRelations;
286 /// Gets or sets the namespace for the XML represenation
287 /// of the data stored in the DataTable.
289 [DataCategory ("Data")]
290 [DataSysDescription ("Indicates the Prefix of the namespace used for this table in XML representation.")]
292 public string Prefix {
293 get { return "" + _prefix; }
294 set { _prefix = value; }
298 /// Gets or sets an array of columns that function as
299 /// primary keys for the data table.
301 [DataCategory ("Data")]
302 [DataSysDescription ("Indicates the column(s) that represent the primary key for this table.")]
303 public DataColumn[] PrimaryKey {
305 UniqueConstraint uc = UniqueConstraint.GetPrimaryKeyConstraint( Constraints);
306 if (null == uc) return new DataColumn[] {};
311 //YUK: msft removes a previous unique constraint if it is flagged as a pk
312 //when a new pk is set
314 //clear Primary Key if value == null
316 UniqueConstraint.SetAsPrimaryKey(this.Constraints, null);
321 //Does constraint exist for these columns
322 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(
323 this.Constraints, (DataColumn[]) value);
325 //if constraint doesn't exist for columns
326 //create new unique primary key constraint
328 uc = new UniqueConstraint( (DataColumn[]) value, true);
330 else { //set existing constraint as the new primary key
331 UniqueConstraint.SetAsPrimaryKey(this.Constraints, uc);
338 /// Gets the collection of_rows that belong to this table.
341 [DataSysDescription ("Indicates the collection that holds the rows of data for this table.")]
342 public DataRowCollection Rows {
343 get { return _rows; }
347 /// Gets or sets an System.ComponentModel.ISite
348 /// for the DataTable.
351 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
352 public override ISite Site {
353 get { return _site; }
354 set { _site = value; }
358 /// Gets or sets the name of the the DataTable.
360 [DataCategory ("Data")]
361 [DataSysDescription ("Indicates the name used to look up this table in the Tables collection of a DataSet.")]
363 [RefreshProperties (RefreshProperties.All)]
364 public string TableName {
365 get { return "" + _tableName; }
366 set { _tableName = value; }
369 bool IListSource.ContainsListCollection {
371 // the collection is a DataView
377 /// Commits all the changes made to this table since the
378 /// last time AcceptChanges was called.
380 public void AcceptChanges ()
382 //FIXME: Do we need to validate anything here or
383 //try to catch any errors to deal with them?
385 foreach(DataRow myRow in _rows) {
386 myRow.AcceptChanges();
391 /// Begins the initialization of a DataTable that is used
392 /// on a form or used by another component. The initialization
393 /// occurs at runtime.
395 public void BeginInit ()
400 /// Turns off notifications, index maintenance, and
401 /// constraints while loading data.
404 public void BeginLoadData ()
409 /// Clears the DataTable of all data.
411 public void Clear () {
416 /// Clones the structure of the DataTable, including
417 /// all DataTable schemas and constraints.
420 public virtual DataTable Clone ()
422 DataTable Copy = new DataTable ();
423 CopyProperties (Copy);
428 /// Computes the given expression on the current_rows that
429 /// pass the filter criteria.
432 public object Compute (string expression, string filter)
434 //FIXME: //Do a real compute
440 /// Copies both the structure and data for this DataTable.
443 public DataTable Copy ()
445 DataTable Copy = new DataTable ();
446 CopyProperties (Copy);
448 foreach (DataRow Row in Rows) {
449 DataRow NewRow = Copy.NewRow ();
450 NewRow.RowError = Row.RowError;
451 foreach (DataColumn C in Copy.Columns) {
452 NewRow [C.ColumnName] = Row [C.ColumnName];
454 Copy.Rows.Add (NewRow);
461 private void CopyProperties (DataTable Copy)
463 Copy.CaseSensitive = CaseSensitive;
464 Copy.VirginCaseSensitive = VirginCaseSensitive;
466 // Copy.ChildRelations
471 Copy.DisplayExpression = DisplayExpression;
472 // Copy.ExtendedProperties
473 Copy.Locale = Locale;
474 Copy.MinimumCapacity = MinimumCapacity;
475 Copy.Namespace = Namespace;
476 // Copy.ParentRelations
477 Copy.Prefix = Prefix;
478 //Copy.PrimaryKey = PrimaryKey;
480 Copy.TableName = TableName;
483 foreach (DataColumn Column in Columns) {
484 Copy.Columns.Add (CopyColumn (Column));
489 /// Ends the initialization of a DataTable that is used
490 /// on a form or used by another component. The
491 /// initialization occurs at runtime.
494 public void EndInit ()
500 /// Turns on notifications, index maintenance, and
501 /// constraints after loading data.
504 public void EndLoadData()
509 /// Gets a copy of the DataTable that contains all
510 /// changes made to it since it was loaded or
511 /// AcceptChanges was last called.
514 public DataTable GetChanges()
521 /// Gets a copy of the DataTable containing all
522 /// changes made to it since it was last loaded, or
523 /// since AcceptChanges was called, filtered by DataRowState.
526 public DataTable GetChanges(DataRowState rowStates)
533 /// Gets an array of DataRow objects that contain errors.
536 public DataRow[] GetErrors ()
538 throw new NotImplementedException ();
542 /// This member is only meant to support Mono's infrastructure
544 protected virtual DataTable CreateInstance ()
546 return Activator.CreateInstance (this.GetType (), true) as DataTable;
550 /// This member is only meant to support Mono's infrastructure
552 protected virtual Type GetRowType ()
554 return typeof (DataRow);
558 /// This member is only meant to support Mono's infrastructure
560 /// Used for Data Binding between System.Web.UI. controls
563 /// System.Windows.Forms controls like a DataGrid
565 IList IListSource.GetList ()
567 IList list = (IList) _defaultView;
572 /// Copies a DataRow into a DataTable, preserving any
573 /// property settings, as well as original and current values.
576 public void ImportRow (DataRow row)
581 /// This member is only meant to support Mono's infrastructure
584 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
589 /// Finds and updates a specific row. If no matching row
590 /// is found, a new row is created using the given values.
593 public DataRow LoadDataRow (object[] values, bool fAcceptChanges)
596 if (PrimaryKey.Length == 0) {
597 row = Rows.Add (values);
599 row.AcceptChanges ();
602 throw new NotImplementedException ();
607 /// Creates a new DataRow with the same schema as the table.
609 public DataRow NewRow ()
611 return this.NewRowFromBuilder (new DataRowBuilder (this, 0, 0));
615 /// This member supports the .NET Framework infrastructure
616 /// and is not intended to be used directly from your code.
618 protected internal DataRow[] NewRowArray (int size)
620 return (DataRow[]) Array.CreateInstance (GetRowType (), size);
624 /// Creates a new row from an existing row.
626 protected virtual DataRow NewRowFromBuilder (DataRowBuilder builder)
628 return new DataRow (builder);
633 /// Rolls back all changes that have been made to the
634 /// table since it was loaded, or the last time AcceptChanges
638 public void RejectChanges ()
640 //foreach(DataRow myRow in _rows)
642 for (int i = _rows.Count - 1; i >= 0; i--) {
643 DataRow row = _rows [i];
644 if (row.RowState != DataRowState.Unchanged)
645 _rows [i].RejectChanges ();
650 /// Resets the DataTable to its original state.
653 public virtual void Reset ()
658 /// Gets an array of all DataRow objects.
660 public DataRow[] Select ()
662 DataRow[] dataRows = new DataRow[_rows.Count];
663 _rows.CopyTo (dataRows, 0);
668 /// Gets an array of all DataRow objects that match
669 /// the filter criteria in order of primary key (or
670 /// lacking one, order of addition.)
672 public DataRow[] Select (string filterExpression)
674 ExpressionElement Expression = new ExpressionMainElement (filterExpression);
676 ArrayList List = new ArrayList ();
677 foreach (DataRow Row in Rows) {
679 if (Expression.Test (Row))
683 return (DataRow [])List.ToArray (typeof (DataRow));
687 /// Gets an array of all DataRow objects that
688 /// match the filter criteria, in the the
689 /// specified sort order.
691 public DataRow[] Select (string filterExpression, string sort)
693 DataRow[] dataRows = null;
695 if (filterExpression != null && filterExpression.Equals (String.Empty) == false)
696 dataRows = Select (filterExpression);
698 dataRows = Select ();
700 if (sort != null && !sort.Equals (String.Empty)) {
701 SortableColumn[] sortableColumns = null;
\r
703 sortableColumns = ParseTheSortString (sort);
\r
704 if (sortableColumns == null)
\r
705 throw new Exception ("sort expression result is null");
\r
706 if (sortableColumns.Length == 0)
\r
707 throw new Exception("sort expression result is 0");
\r
709 RowSorter rowSorter = new RowSorter (dataRows, sortableColumns);
\r
710 dataRows = rowSorter.SortRows ();
712 sortableColumns = null;
720 /// Gets an array of all DataRow objects that match
721 /// the filter in the order of the sort, that match
722 /// the specified state.
725 public DataRow[] Select(string filterExpression, string sort, DataViewRowState recordStates)
727 DataRow[] dataRows = null;
729 // TODO: do something with recordStates
730 dataRows = Select (filterExpression, sort);
736 /// Gets the TableName and DisplayExpression, if
737 /// there is one as a concatenated string.
739 public override string ToString()
741 //LAMESPEC: spec says concat the two. impl puts a
742 //plus sign infront of DisplayExpression
743 return TableName + " " + DisplayExpression;
747 #region Events /////////////////
750 /// Raises the ColumnChanged event.
752 protected virtual void OnColumnChanged (DataColumnChangeEventArgs e) {
753 if (null != ColumnChanged) {
754 ColumnChanged (this, e);
759 /// Raises the ColumnChanging event.
761 protected virtual void OnColumnChanging (DataColumnChangeEventArgs e) {
762 if (null != ColumnChanging) {
763 ColumnChanging (this, e);
768 /// Raises the PropertyChanging event.
771 protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent) {
772 // if (null != PropertyChanging)
774 // PropertyChanging (this, e);
779 /// Notifies the DataTable that a DataColumn is being removed.
782 protected internal virtual void OnRemoveColumn (DataColumn column) {
783 // if (null != RemoveColumn)
785 // RemoveColumn(this, e);
790 /// Raises the RowChanged event.
792 protected virtual void OnRowChanged (DataRowChangeEventArgs e) {
793 if (null != RowChanged) {
800 /// Raises the RowChanging event.
802 protected virtual void OnRowChanging (DataRowChangeEventArgs e) {
803 if (null != RowChanging) {
804 RowChanging(this, e);
809 /// Raises the RowDeleted event.
811 protected virtual void OnRowDeleted (DataRowChangeEventArgs e) {
812 if (null != RowDeleted) {
818 /// Raises the RowDeleting event.
820 protected virtual void OnRowDeleting (DataRowChangeEventArgs e) {
821 if (null != RowDeleting) {
822 RowDeleting(this, e);
827 private DataColumn CopyColumn (DataColumn Column) {
828 DataColumn Copy = new DataColumn ();
830 // Copy all the properties of column
831 Copy.AllowDBNull = Column.AllowDBNull;
832 Copy.AutoIncrement = Column.AutoIncrement;
833 Copy.AutoIncrementSeed = Column.AutoIncrementSeed;
834 Copy.AutoIncrementStep = Column.AutoIncrementStep;
835 Copy.Caption = Column.Caption;
836 Copy.ColumnMapping = Column.ColumnMapping;
837 Copy.ColumnName = Column.ColumnName;
841 Copy.Expression = Column.Expression;
842 //Copy.ExtendedProperties
843 Copy.MaxLength = Column.MaxLength;
844 Copy.Namespace = Column.Namespace;
845 Copy.Prefix = Column.Prefix;
846 Copy.ReadOnly = Column.ReadOnly;
848 Copy.Unique = Column.Unique;
854 /// Occurs when after a value has been changed for
855 /// the specified DataColumn in a DataRow.
857 [DataCategory ("Data")]
858 [DataSysDescription ("Occurs when a value has been changed for this column.")]
859 public event DataColumnChangeEventHandler ColumnChanged;
862 /// Occurs when a value is being changed for the specified
863 /// DataColumn in a DataRow.
865 [DataCategory ("Data")]
866 [DataSysDescription ("Occurs when a value has been submitted for this column. The user can modify the proposed value and should throw an exception to cancel the edit.")]
867 public event DataColumnChangeEventHandler ColumnChanging;
870 /// Occurs after a DataRow has been changed successfully.
872 [DataCategory ("Data")]
873 [DataSysDescription ("Occurs after a row in the table has been successfully edited.")]
874 public event DataRowChangeEventHandler RowChanged;
877 /// Occurs when a DataRow is changing.
879 [DataCategory ("Data")]
880 [DataSysDescription ("Occurs when the row is being changed so that the event handler can modify or cancel the change. The user can modify values in the row and should throw an exception to cancel the edit.")]
881 public event DataRowChangeEventHandler RowChanging;
884 /// Occurs after a row in the table has been deleted.
886 [DataCategory ("Data")]
887 [DataSysDescription ("Occurs after a row in the table has been successfully deleted.")]
888 public event DataRowChangeEventHandler RowDeleted;
891 /// Occurs before a row in the table is about to be deleted.
893 [DataCategory ("Data")]
894 [DataSysDescription ("Occurs when a row in the table marked for deletion. Throw an exception to cancel the deletion.")]
895 public event DataRowChangeEventHandler RowDeleting;
899 // to parse the sort string for DataTable:Select(expression,sort)
900 // into sortable columns (think ORDER BY,
901 // such as, "customer ASC, price DESC" )
902 private SortableColumn[] ParseTheSortString (string sort)
\r
904 SortableColumn[] sortColumns = null;
\r
905 ArrayList columns = null;
\r
907 if (sort != null && !sort.Equals ("")) {
\r
908 columns = new ArrayList ();
\r
909 string[] columnExpression = sort.Trim ().Split (new char[1] {','});
\r
911 for (int c = 0; c < columnExpression.Length; c++) {
\r
912 string[] columnSortInfo = columnExpression[c].Trim ().Split (new char[1] {' '});
\r
914 string columnName = columnSortInfo[0].Trim ();
\r
915 string sortOrder = "ASC";
\r
916 if (columnSortInfo.Length > 1)
\r
917 sortOrder = columnSortInfo[1].Trim ().ToUpper ();
\r
919 ListSortDirection sortDirection = ListSortDirection.Ascending;
\r
920 switch (sortOrder) {
\r
922 sortDirection = ListSortDirection.Ascending;
\r
925 sortDirection = ListSortDirection.Descending;
\r
928 throw new IndexOutOfRangeException ("Could not find column: " + columnExpression[c]);
\r
932 ord = Int32.Parse (columnName);
\r
934 catch (FormatException) {
\r
937 DataColumn dc = null;
\r
939 dc = _columnCollection[columnName];
\r
941 dc = _columnCollection[ord];
\r
942 SortableColumn sortCol = new SortableColumn (dc,sortDirection);
\r
943 columns.Add (sortCol);
\r
945 sortColumns = (SortableColumn[]) columns.ToArray (typeof (SortableColumn));
\r
947 return sortColumns;
\r
950 private class SortableColumn
\r
952 private DataColumn col;
\r
953 private ListSortDirection dir;
\r
955 internal SortableColumn (DataColumn column,
\r
956 ListSortDirection direction)
\r
962 public DataColumn Column {
\r
968 public ListSortDirection SortDirection {
\r
975 private class RowSorter : IComparer
\r
977 private SortableColumn[] sortColumns;
\r
978 private DataRow[] rowsToSort;
\r
980 internal RowSorter(DataRow[] unsortedRows,
\r
981 SortableColumn[] sortColumns)
\r
983 this.sortColumns = sortColumns;
\r
984 this.rowsToSort = unsortedRows;
\r
987 public SortableColumn[] SortColumns {
\r
989 return sortColumns;
\r
993 public DataRow[] SortRows ()
\r
995 Array.Sort (rowsToSort, this);
\r
999 int IComparer.Compare (object x, object y)
\r
1002 throw new Exception ("Object to compare is null: x");
\r
1004 throw new Exception ("Object to compare is null: y");
\r
1005 if(!(x is DataRow))
\r
1006 throw new Exception ("Object to compare is not DataRow: x is " + x.GetType().ToString());
\r
1007 if(!(y is DataRow))
\r
1008 throw new Exception ("Object to compare is not DataRow: y is " + x.GetType().ToString());
\r
1010 DataRow rowx = (DataRow) x;
\r
1011 DataRow rowy = (DataRow) y;
\r
1013 for(int i = 0; i < sortColumns.Length; i++) {
\r
1014 SortableColumn sortColumn = sortColumns[i];
\r
1015 DataColumn dc = sortColumn.Column;
\r
1017 IComparable objx = (IComparable) rowx[dc];
\r
1018 object objy = rowy[dc];
\r
1020 int result = CompareObjects (objx, objy);
\r
1021 if (result != 0) {
\r
1022 if (sortColumn.SortDirection == ListSortDirection.Ascending) {
\r
1033 private int CompareObjects (object a, object b)
\r
1039 else if (a == DBNull.Value)
1043 else if (b == DBNull.Value)
1046 if((a is string) && (b is string)) {
1047 a = ((string) a).ToUpper ();
1048 b = ((string) b).ToUpper ();
1051 if (a is IComparable)
1052 return ((a as IComparable).CompareTo (b));
1053 else if (b is IComparable)
1054 return -((b as IComparable).CompareTo (a));
1056 throw new ArgumentException ("Neither a nor b IComparable");