ObjectStorage.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. //------------------------------------------------------------------------------
  2. // <copyright file="ObjectStorage.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">Microsoft</owner>
  6. // <owner current="true" primary="false">Microsoft</owner>
  7. //------------------------------------------------------------------------------
  8. namespace System.Data.Common {
  9. using System;
  10. using System.Data;
  11. using System.Xml;
  12. using System.IO;
  13. using System.Xml.Serialization;
  14. using System.Collections;
  15. using System.Collections.Generic;
  16. using System.Diagnostics;
  17. using System.Runtime.CompilerServices;
  18. internal sealed class ObjectStorage : DataStorage {
  19. static private readonly Object defaultValue = null;
  20. private enum Families { DATETIME, NUMBER, STRING, BOOLEAN, ARRAY };
  21. private object[] values;
  22. private readonly bool implementsIXmlSerializable;
  23. internal ObjectStorage(DataColumn column, Type type)
  24. : base(column, type, defaultValue, DBNull.Value, typeof(ICloneable).IsAssignableFrom(type), GetStorageType(type)) {
  25. implementsIXmlSerializable = typeof(IXmlSerializable).IsAssignableFrom(type);
  26. }
  27. override public Object Aggregate(int[] records, AggregateType kind) {
  28. throw ExceptionBuilder.AggregateException(kind, DataType);
  29. }
  30. override public int Compare(int recordNo1, int recordNo2) {
  31. object valueNo1 = values[recordNo1];
  32. object valueNo2 = values[recordNo2];
  33. if (valueNo1 == valueNo2)
  34. return 0;
  35. if (valueNo1 == null)
  36. return -1;
  37. if (valueNo2 == null)
  38. return 1;
  39. IComparable icomparable = (valueNo1 as IComparable);
  40. if (null != icomparable) {
  41. try {
  42. return icomparable.CompareTo(valueNo2);
  43. }
  44. catch(ArgumentException e) {
  45. ExceptionBuilder.TraceExceptionWithoutRethrow(e);
  46. }
  47. }
  48. return CompareWithFamilies(valueNo1, valueNo2);
  49. }
  50. override public int CompareValueTo(int recordNo1, Object value) {
  51. object valueNo1 = Get(recordNo1);
  52. if (valueNo1 is IComparable) {
  53. if (value.GetType() == valueNo1.GetType())
  54. return((IComparable) valueNo1).CompareTo(value);
  55. }
  56. if (valueNo1 == value)
  57. return 0;
  58. if (valueNo1 == null) {
  59. if (NullValue == value) {
  60. return 0;
  61. }
  62. return -1;
  63. }
  64. if ((NullValue == value) || (null == value)) {
  65. return 1;
  66. }
  67. return CompareWithFamilies(valueNo1, value);
  68. }
  69. private int CompareTo(object valueNo1, object valueNo2) {
  70. if (valueNo1 == null)
  71. return -1;
  72. if (valueNo2 == null)
  73. return 1;
  74. if (valueNo1 == valueNo2)
  75. return 0;
  76. if (valueNo1 == NullValue)
  77. return -1;
  78. if (valueNo2 == NullValue)
  79. return 1;
  80. if (valueNo1 is IComparable) {
  81. try{
  82. return ((IComparable) valueNo1).CompareTo(valueNo2);
  83. }
  84. catch(ArgumentException e) {
  85. ExceptionBuilder.TraceExceptionWithoutRethrow(e);
  86. }
  87. }
  88. return CompareWithFamilies(valueNo1, valueNo2);
  89. }
  90. private int CompareWithFamilies(Object valueNo1, Object valueNo2) {
  91. Families Family1 = GetFamily(valueNo1.GetType());
  92. Families Family2 = GetFamily(valueNo2.GetType());
  93. if (Family1 < Family2)
  94. return -1;
  95. else
  96. if (Family1 > Family2)
  97. return 1;
  98. else {
  99. switch (Family1) {
  100. case Families.BOOLEAN :
  101. valueNo1 = Convert.ToBoolean(valueNo1, FormatProvider);
  102. valueNo2 = Convert.ToBoolean(valueNo2, FormatProvider);
  103. break;
  104. case Families.DATETIME:
  105. valueNo1 = Convert.ToDateTime(valueNo1, FormatProvider);
  106. valueNo2 = Convert.ToDateTime(valueNo1, FormatProvider);
  107. break;
  108. case Families.NUMBER :
  109. valueNo1 = Convert.ToDouble(valueNo1, FormatProvider);
  110. valueNo2 = Convert.ToDouble(valueNo2, FormatProvider);
  111. break;
  112. case Families.ARRAY :{
  113. Array arr1 = (Array) valueNo1;
  114. Array arr2 = (Array) valueNo2;
  115. if (arr1.Length > arr2.Length)
  116. return 1;
  117. else if (arr1.Length < arr2.Length)
  118. return -1;
  119. else { // same number of elements
  120. for (int i = 0; i < arr1.Length; i++){
  121. int c = CompareTo(arr1.GetValue(i),arr2.GetValue(i));
  122. if (c != 0)
  123. return c ;
  124. }
  125. }
  126. return 0;
  127. }
  128. default :
  129. valueNo1 = valueNo1.ToString();
  130. valueNo2 = valueNo2.ToString();
  131. break;
  132. }
  133. return ((IComparable) valueNo1).CompareTo(valueNo2);
  134. }
  135. }
  136. override public void Copy(int recordNo1, int recordNo2) {
  137. values[recordNo2] = values[recordNo1];
  138. }
  139. override public Object Get(int recordNo) {
  140. Object value = values[recordNo];
  141. if (null != value) {
  142. return value;
  143. }
  144. return NullValue;
  145. }
  146. private Families GetFamily(Type dataType) {
  147. switch (Type.GetTypeCode(dataType)) {
  148. case TypeCode.Boolean: return Families.BOOLEAN;
  149. case TypeCode.Char: return Families.STRING;
  150. case TypeCode.SByte: return Families.STRING;
  151. case TypeCode.Byte: return Families.STRING;
  152. case TypeCode.Int16: return Families.NUMBER;
  153. case TypeCode.UInt16: return Families.NUMBER;
  154. case TypeCode.Int32: return Families.NUMBER;
  155. case TypeCode.UInt32: return Families.NUMBER;
  156. case TypeCode.Int64: return Families.NUMBER;
  157. case TypeCode.UInt64: return Families.NUMBER;
  158. case TypeCode.Single: return Families.NUMBER;
  159. case TypeCode.Double: return Families.NUMBER;
  160. case TypeCode.Decimal: return Families.NUMBER;
  161. case TypeCode.DateTime: return Families.DATETIME;
  162. case TypeCode.String: return Families.STRING;
  163. default:
  164. if (typeof(TimeSpan) == dataType) {
  165. return Families.DATETIME;
  166. }
  167. else if(dataType.IsArray) {
  168. return Families.ARRAY;
  169. }
  170. else{
  171. return Families.STRING;
  172. }
  173. }
  174. }
  175. override public bool IsNull(int record) {
  176. return (null == values[record]);
  177. }
  178. override public void Set(int recordNo, Object value) {
  179. System.Diagnostics.Debug.Assert(null != value, "null value");
  180. if (NullValue == value) {
  181. values[recordNo] = null;
  182. }
  183. else if (DataType == typeof(Object) || DataType.IsInstanceOfType(value)) {
  184. values[recordNo] = value;
  185. }
  186. else {
  187. Type valType = value.GetType();
  188. if (DataType == typeof(Guid) && valType == typeof(string)){
  189. values[recordNo] = new Guid((string)value);
  190. }
  191. else if (DataType == typeof(byte[])) {
  192. if (valType == typeof(Boolean)){
  193. values[recordNo] = BitConverter.GetBytes((Boolean)value);
  194. }
  195. else if (valType == typeof(Char)){
  196. values[recordNo] = BitConverter.GetBytes((Char)value);
  197. }
  198. else if (valType == typeof(Int16)){
  199. values[recordNo] = BitConverter.GetBytes((Int16)value);
  200. }
  201. else if (valType == typeof(Int32)){
  202. values[recordNo] = BitConverter.GetBytes((Int32)value);
  203. }
  204. else if (valType == typeof(Int64)){
  205. values[recordNo] = BitConverter.GetBytes((Int64)value);
  206. }
  207. else if (valType == typeof(UInt16)){
  208. values[recordNo] = BitConverter.GetBytes((UInt16)value);
  209. }
  210. else if (valType == typeof(UInt32)){
  211. values[recordNo] = BitConverter.GetBytes((UInt32)value);
  212. }
  213. else if (valType == typeof(UInt64)){
  214. values[recordNo] = BitConverter.GetBytes((UInt64)value);
  215. }
  216. else if (valType == typeof(Single)){
  217. values[recordNo] = BitConverter.GetBytes((Single)value);
  218. }
  219. else if (valType == typeof(Double)){
  220. values[recordNo] = BitConverter.GetBytes((Double)value);
  221. }
  222. else {
  223. throw ExceptionBuilder.StorageSetFailed();
  224. }
  225. }
  226. else {
  227. throw ExceptionBuilder.StorageSetFailed();
  228. }
  229. }
  230. }
  231. override public void SetCapacity(int capacity) {
  232. object[] newValues = new object[capacity];
  233. if (values != null) {
  234. Array.Copy(values, 0, newValues, 0, Math.Min(capacity, values.Length));
  235. }
  236. values = newValues;
  237. }
  238. // Prevent inlining so that reflection calls are not moved to caller that may be in a different assembly that may have a different grant set.
  239. [MethodImpl(MethodImplOptions.NoInlining)]
  240. override public object ConvertXmlToObject(string s) {
  241. Type type = DataType; // real type of objects in this column
  242. if (type == typeof(byte[])) {
  243. return Convert.FromBase64String(s);
  244. }
  245. if (type == typeof(Type)){
  246. return Type.GetType(s);
  247. }
  248. if (type == typeof (Guid)){
  249. return (new Guid(s));
  250. }
  251. if (type == typeof (Uri)){
  252. return (new Uri(s));
  253. }
  254. if (implementsIXmlSerializable) {
  255. object Obj = System.Activator.CreateInstance(DataType, true);
  256. StringReader strReader = new StringReader(s);
  257. using (XmlTextReader xmlTextReader = new XmlTextReader(strReader)) {
  258. ((IXmlSerializable)Obj).ReadXml(xmlTextReader);
  259. }
  260. return Obj;
  261. }
  262. StringReader strreader = new StringReader(s);
  263. XmlSerializer deserializerWithOutRootAttribute = ObjectStorage.GetXmlSerializer(type);
  264. return(deserializerWithOutRootAttribute.Deserialize(strreader));
  265. }
  266. // Prevent inlining so that reflection calls are not moved to caller that may be in a different assembly that may have a different grant set.
  267. [MethodImpl(MethodImplOptions.NoInlining)]
  268. public override object ConvertXmlToObject(XmlReader xmlReader, XmlRootAttribute xmlAttrib) {
  269. object retValue = null;
  270. bool isBaseCLRType = false;
  271. bool legacyUDT = false; // in 1.0 and 1.1 we used to call ToString on CDT obj. so if we have the same case
  272. // we need to handle the case when we have column type as object.
  273. if (null == xmlAttrib) { // this means type implements IXmlSerializable
  274. Type type = null;
  275. string typeName = xmlReader.GetAttribute(Keywords.MSD_INSTANCETYPE, Keywords.MSDNS);
  276. if (typeName == null || typeName.Length == 0) { // No CDT polumorphism
  277. string xsdTypeName = xmlReader.GetAttribute(Keywords.TYPE, Keywords.XSINS); // this xsd type: Base type polymorphism
  278. if (null != xsdTypeName && xsdTypeName.Length > 0) {
  279. string [] _typename = xsdTypeName.Split(':');
  280. if (_typename.Length == 2) { // split will return aray of size 1 if ":" is not there
  281. if (xmlReader.LookupNamespace(_typename[0]) == Keywords.XSDNS) {
  282. xsdTypeName = _typename[1]; // trim the prefix and just continue with
  283. }
  284. } // for other case, let say we have two ':' in type, the we throws (as old behavior)
  285. type = XSDSchema.XsdtoClr(xsdTypeName);
  286. isBaseCLRType = true;
  287. }
  288. else if (DataType == typeof(object)) {// there is no Keywords.MSD_INSTANCETYPE and no Keywords.TYPE
  289. legacyUDT = true; // see if our type is object
  290. }
  291. }
  292. if (legacyUDT) { // if Everett UDT, just read it and return string
  293. retValue = xmlReader.ReadString();
  294. }
  295. else {
  296. if (typeName == Keywords.TYPEINSTANCE) {
  297. retValue = Type.GetType(xmlReader.ReadString());
  298. xmlReader.Read(); // need to move to next node
  299. }
  300. else {
  301. if (null == type) {
  302. type = (typeName == null)? DataType : DataStorage.GetType(typeName);
  303. }
  304. if (type == typeof(char) || type == typeof(Guid)) { //msdata:char and msdata:guid imply base types.
  305. isBaseCLRType=true;
  306. }
  307. if (type == typeof(object))
  308. throw ExceptionBuilder.CanNotDeserializeObjectType();
  309. if (!isBaseCLRType){
  310. retValue = System.Activator.CreateInstance (type, true);
  311. Debug.Assert(xmlReader is DataTextReader, "Invalid DataTextReader is being passed to customer");
  312. ((IXmlSerializable)retValue).ReadXml(xmlReader);
  313. }
  314. else { // Process Base CLR type
  315. // for Element Node, if it is Empty, ReadString does not move to End Element; we need to move it
  316. if(type == typeof(string) && xmlReader.NodeType == XmlNodeType.Element && xmlReader.IsEmptyElement) {
  317. retValue = string.Empty;
  318. }
  319. else {
  320. retValue = xmlReader.ReadString();
  321. if (type != typeof(byte[])) {
  322. retValue = SqlConvert.ChangeTypeForXML(retValue, type);
  323. }
  324. else {
  325. retValue = Convert.FromBase64String(retValue.ToString());
  326. }
  327. }
  328. xmlReader.Read();
  329. }
  330. }
  331. }
  332. }
  333. else{
  334. XmlSerializer deserializerWithRootAttribute = ObjectStorage.GetXmlSerializer(DataType, xmlAttrib);
  335. retValue = deserializerWithRootAttribute.Deserialize(xmlReader);
  336. }
  337. return retValue;
  338. }
  339. override public string ConvertObjectToXml(object value) {
  340. if ((value == null) || (value == NullValue))// this case wont happen, this is added in case if code in xml saver changes
  341. return String.Empty;
  342. Type type = DataType;
  343. if (type == typeof(byte[]) || (type == typeof(object) && (value is byte[]))) {
  344. return Convert.ToBase64String((byte[])value);
  345. }
  346. if ((type == typeof(Type)) || ((type == typeof(Object)) && (value is Type))) {
  347. return ((Type)value).AssemblyQualifiedName;
  348. }
  349. if (!IsTypeCustomType(value.GetType())){ // Guid and Type had TypeCode.Object
  350. return (string)SqlConvert.ChangeTypeForXML(value, typeof(string));
  351. }
  352. if (Type.GetTypeCode(value.GetType()) != TypeCode.Object) {
  353. return value.ToString();
  354. }
  355. StringWriter strwriter = new StringWriter(FormatProvider);
  356. if (implementsIXmlSerializable) {
  357. using (XmlTextWriter xmlTextWriter = new XmlTextWriter (strwriter)) {
  358. ((IXmlSerializable)value).WriteXml(xmlTextWriter);
  359. }
  360. return (strwriter.ToString());
  361. }
  362. XmlSerializer serializerWithOutRootAttribute = ObjectStorage.GetXmlSerializer(value.GetType());
  363. serializerWithOutRootAttribute.Serialize( strwriter, value);
  364. return strwriter.ToString();
  365. }
  366. public override void ConvertObjectToXml(object value, XmlWriter xmlWriter, XmlRootAttribute xmlAttrib) {
  367. if (null == xmlAttrib) { // implements IXmlSerializable
  368. Debug.Assert(xmlWriter is DataTextWriter, "Invalid DataTextWriter is being passed to customer");
  369. ((IXmlSerializable)value).WriteXml(xmlWriter);
  370. }
  371. else {
  372. XmlSerializer serializerWithRootAttribute = ObjectStorage.GetXmlSerializer(value.GetType(), xmlAttrib);
  373. serializerWithRootAttribute.Serialize(xmlWriter, value);
  374. }
  375. }
  376. override protected object GetEmptyStorage(int recordCount) {
  377. return new Object[recordCount];
  378. }
  379. override protected void CopyValue(int record, object store, BitArray nullbits, int storeIndex) {
  380. Object[] typedStore = (Object[]) store;
  381. typedStore[storeIndex] = values[record];
  382. bool isNull = IsNull(record);
  383. nullbits.Set(storeIndex, isNull);
  384. if (!isNull && (typedStore[storeIndex] is DateTime)) {
  385. DateTime dt = (DateTime)typedStore[storeIndex];
  386. if (dt.Kind == DateTimeKind.Local) {
  387. typedStore[storeIndex] = DateTime.SpecifyKind(dt.ToUniversalTime(), DateTimeKind.Local);
  388. }
  389. }
  390. }
  391. override protected void SetStorage(object store, BitArray nullbits) {
  392. values = (Object[]) store;
  393. for(int i = 0; i < values.Length; i++) {
  394. if (values[i] is DateTime) {
  395. DateTime dt = (DateTime) values[i];
  396. if (dt.Kind == DateTimeKind.Local) {
  397. values[i] = (DateTime.SpecifyKind(dt, DateTimeKind.Utc)).ToLocalTime();
  398. }
  399. }
  400. }
  401. // SetNullStorage(nullbits); -> No need to set bits,
  402. }
  403. // SQLBU 431443: dynamically generated assemblies not cached for XmlSerialization when serializable Udt does not implement IXmlSerializable, "memeory leak"
  404. private static readonly object _tempAssemblyCacheLock = new object();
  405. private static Dictionary<KeyValuePair<Type,XmlRootAttribute>, XmlSerializer> _tempAssemblyCache;
  406. private static readonly XmlSerializerFactory _serializerFactory = new XmlSerializerFactory();
  407. /// <summary>
  408. /// throw an InvalidOperationException if type implements IDynamicMetaObjectProvider and not IXmlSerializable
  409. /// because XmlSerializerFactory will only serialize the type's declared properties, not its dynamic properties
  410. /// </summary>
  411. /// <param name="type">type to test for IDynamicMetaObjectProvider</param>
  412. /// <exception cref="InvalidOperationException">DataSet will not serialize types that implement IDynamicMetaObjectProvider but do not also implement IXmlSerializable</exception>
  413. /// <remarks>IDynamicMetaObjectProvider was introduced in .Net Framework V4.0 into System.Core</remarks>
  414. internal static void VerifyIDynamicMetaObjectProvider(Type type)
  415. {
  416. if (typeof(System.Dynamic.IDynamicMetaObjectProvider).IsAssignableFrom(type) &&
  417. !typeof(System.Xml.Serialization.IXmlSerializable).IsAssignableFrom(type))
  418. {
  419. throw ADP.InvalidOperation(Res.GetString(Res.Xml_DynamicWithoutXmlSerializable));
  420. }
  421. }
  422. internal static XmlSerializer GetXmlSerializer(Type type)
  423. {
  424. // Dev10 671061: prevent writing an instance which implements IDynamicMetaObjectProvider and not IXmlSerializable
  425. // the check here prevents the instance data from being written
  426. VerifyIDynamicMetaObjectProvider(type);
  427. // use factory which caches XmlSerializer as necessary
  428. XmlSerializer serializer = _serializerFactory.CreateSerializer(type);
  429. return serializer;
  430. }
  431. internal static XmlSerializer GetXmlSerializer(Type type, XmlRootAttribute attribute)
  432. {
  433. XmlSerializer serializer = null;
  434. KeyValuePair<Type,XmlRootAttribute> key = new KeyValuePair<Type,XmlRootAttribute>(type,attribute);
  435. // _tempAssemblyCache is a readonly instance, lock on write to copy & add then replace the original instance.
  436. Dictionary<KeyValuePair<Type,XmlRootAttribute>, XmlSerializer> cache = _tempAssemblyCache;
  437. if ((null == cache) || !cache.TryGetValue(key, out serializer))
  438. { // not in cache, try again with lock because it may need to grow
  439. lock(_tempAssemblyCacheLock)
  440. {
  441. cache = _tempAssemblyCache;
  442. if ((null == cache) || !cache.TryGetValue(key, out serializer))
  443. {
  444. // Dev10 671061: prevent writing an instance which implements IDynamicMetaObjectProvider and not IXmlSerializable
  445. // the check here prevents the instance data from being written
  446. VerifyIDynamicMetaObjectProvider(type);
  447. // if still not in cache, make cache larger and add new XmlSerializer
  448. if (null != cache)
  449. { // create larger cache, because dictionary is not reader/writer safe
  450. // copy cache so that all readers don't take lock - only potential new writers
  451. // same logic used by DbConnectionFactory
  452. Dictionary<KeyValuePair<Type,XmlRootAttribute>, XmlSerializer> tmp =
  453. new Dictionary<KeyValuePair<Type,XmlRootAttribute>, XmlSerializer>(
  454. 1+cache.Count, TempAssemblyComparer.Default);
  455. foreach (KeyValuePair<KeyValuePair<Type,XmlRootAttribute>, XmlSerializer> entry in cache)
  456. { // copy contents from old cache to new cache
  457. tmp.Add(entry.Key, entry.Value);
  458. }
  459. cache = tmp;
  460. }
  461. else
  462. { // initial creation of cache
  463. cache = new Dictionary<KeyValuePair<Type,XmlRootAttribute>, XmlSerializer>(
  464. TempAssemblyComparer.Default);
  465. }
  466. // attribute is modifiable - but usuage from XmlSaver & XmlDataLoader & XmlDiffLoader
  467. // the instances are not modified, but to be safe - copy the XmlRootAttribute before caching
  468. key = new KeyValuePair<Type,XmlRootAttribute>(type, new XmlRootAttribute());
  469. key.Value.ElementName = attribute.ElementName;
  470. key.Value.Namespace = attribute.Namespace;
  471. key.Value.DataType = attribute.DataType;
  472. key.Value.IsNullable = attribute.IsNullable;
  473. serializer = _serializerFactory.CreateSerializer(type, attribute);
  474. cache.Add(key, serializer);
  475. _tempAssemblyCache = cache;
  476. }
  477. }
  478. }
  479. return serializer;
  480. }
  481. private class TempAssemblyComparer : IEqualityComparer<KeyValuePair<Type,XmlRootAttribute>>
  482. {
  483. internal static readonly IEqualityComparer<KeyValuePair<Type,XmlRootAttribute>> Default = new TempAssemblyComparer();
  484. private TempAssemblyComparer() { }
  485. public bool Equals(KeyValuePair<Type,XmlRootAttribute> x, KeyValuePair<Type,XmlRootAttribute> y)
  486. {
  487. return ((x.Key == y.Key) && // same type
  488. (((x.Value == null) && (y.Value == null)) || // XmlRootAttribute both null
  489. ((x.Value != null) && (y.Value != null) && // XmlRootAttribute both with value
  490. (x.Value.ElementName == y.Value.ElementName) && // all attribute elements are equal
  491. (x.Value.Namespace == y.Value.Namespace) &&
  492. (x.Value.DataType == y.Value.DataType) &&
  493. (x.Value.IsNullable == y.Value.IsNullable))));
  494. }
  495. public int GetHashCode(KeyValuePair<Type,XmlRootAttribute> obj)
  496. {
  497. return unchecked(obj.Key.GetHashCode() + obj.Value.ElementName.GetHashCode());
  498. }
  499. }
  500. }
  501. }