1 /* **************************************************************************** 2 * 3 * Copyright (c) Microsoft Corporation. 4 * 5 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 6 * copy of the license can be found in the License.html file at the root of this distribution. If 7 * you cannot locate the Apache License, Version 2.0, please send an email to 8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 9 * by the terms of the Apache License, Version 2.0. 10 * 11 * You must not remove this notice, or any other, from this software. 12 * 13 * 14 * ***************************************************************************/ 15 16 #if CLR2 17 using Microsoft.Scripting.Ast; 18 #else 19 using System.Linq.Expressions; 20 #endif 21 #if SILVERLIGHT 22 using System.Core; 23 #endif 24 25 using System.Collections.Generic; 26 using System.Diagnostics; 27 using System.Reflection; 28 29 namespace System.Dynamic.Utils { 30 31 internal static class TypeUtils { 32 private const BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; 33 internal const MethodAttributes PublicStatic = MethodAttributes.Public | MethodAttributes.Static; 34 GetNonNullableType(this Type type)35 internal static Type GetNonNullableType(this Type type) { 36 if (IsNullableType(type)) { 37 return type.GetGenericArguments()[0]; 38 } 39 return type; 40 } 41 GetNullableType(Type type)42 internal static Type GetNullableType(Type type) { 43 Debug.Assert(type != null, "type cannot be null"); 44 if (type.IsValueType && !IsNullableType(type)) { 45 return typeof(Nullable<>).MakeGenericType(type); 46 } 47 return type; 48 } 49 IsNullableType(this Type type)50 internal static bool IsNullableType(this Type type) { 51 return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); 52 } 53 IsBool(Type type)54 internal static bool IsBool(Type type) { 55 return GetNonNullableType(type) == typeof(bool); 56 } 57 IsNumeric(Type type)58 internal static bool IsNumeric(Type type) { 59 type = GetNonNullableType(type); 60 if (!type.IsEnum) { 61 switch (Type.GetTypeCode(type)) { 62 case TypeCode.Char: 63 case TypeCode.SByte: 64 case TypeCode.Byte: 65 case TypeCode.Int16: 66 case TypeCode.Int32: 67 case TypeCode.Int64: 68 case TypeCode.Double: 69 case TypeCode.Single: 70 case TypeCode.UInt16: 71 case TypeCode.UInt32: 72 case TypeCode.UInt64: 73 return true; 74 } 75 } 76 return false; 77 } 78 IsInteger(Type type)79 internal static bool IsInteger(Type type) { 80 type = GetNonNullableType(type); 81 if (type.IsEnum) { 82 return false; 83 } 84 switch (Type.GetTypeCode(type)) { 85 case TypeCode.Byte: 86 case TypeCode.SByte: 87 case TypeCode.Int16: 88 case TypeCode.Int32: 89 case TypeCode.Int64: 90 case TypeCode.UInt16: 91 case TypeCode.UInt32: 92 case TypeCode.UInt64: 93 return true; 94 default: 95 return false; 96 } 97 } 98 99 IsArithmetic(Type type)100 internal static bool IsArithmetic(Type type) { 101 type = GetNonNullableType(type); 102 if (!type.IsEnum) { 103 switch (Type.GetTypeCode(type)) { 104 case TypeCode.Int16: 105 case TypeCode.Int32: 106 case TypeCode.Int64: 107 case TypeCode.Double: 108 case TypeCode.Single: 109 case TypeCode.UInt16: 110 case TypeCode.UInt32: 111 case TypeCode.UInt64: 112 return true; 113 } 114 } 115 return false; 116 } 117 IsUnsignedInt(Type type)118 internal static bool IsUnsignedInt(Type type) { 119 type = GetNonNullableType(type); 120 if (!type.IsEnum) { 121 switch (Type.GetTypeCode(type)) { 122 case TypeCode.UInt16: 123 case TypeCode.UInt32: 124 case TypeCode.UInt64: 125 return true; 126 } 127 } 128 return false; 129 } 130 IsIntegerOrBool(Type type)131 internal static bool IsIntegerOrBool(Type type) { 132 type = GetNonNullableType(type); 133 if (!type.IsEnum) { 134 switch (Type.GetTypeCode(type)) { 135 case TypeCode.Int64: 136 case TypeCode.Int32: 137 case TypeCode.Int16: 138 case TypeCode.UInt64: 139 case TypeCode.UInt32: 140 case TypeCode.UInt16: 141 case TypeCode.Boolean: 142 case TypeCode.SByte: 143 case TypeCode.Byte: 144 return true; 145 } 146 } 147 return false; 148 } 149 AreEquivalent(Type t1, Type t2)150 internal static bool AreEquivalent(Type t1, Type t2) 151 { 152 #if CLR2 || SILVERLIGHT 153 return t1 == t2; 154 #else 155 return t1 == t2 || t1.IsEquivalentTo(t2); 156 #endif 157 } 158 AreReferenceAssignable(Type dest, Type src)159 internal static bool AreReferenceAssignable(Type dest, Type src) { 160 // WARNING: This actually implements "Is this identity assignable and/or reference assignable?" 161 if (AreEquivalent(dest, src)) { 162 return true; 163 } 164 if (!dest.IsValueType && !src.IsValueType && dest.IsAssignableFrom(src)) { 165 return true; 166 } 167 return false; 168 } 169 170 // Checks if the type is a valid target for an instance call IsValidInstanceType(MemberInfo member, Type instanceType)171 internal static bool IsValidInstanceType(MemberInfo member, Type instanceType) { 172 Type targetType = member.DeclaringType; 173 if (AreReferenceAssignable(targetType, instanceType)) { 174 return true; 175 } 176 if (instanceType.IsValueType) { 177 if (AreReferenceAssignable(targetType, typeof(System.Object))) { 178 return true; 179 } 180 if (AreReferenceAssignable(targetType, typeof(System.ValueType))) { 181 return true; 182 } 183 if (instanceType.IsEnum && AreReferenceAssignable(targetType, typeof(System.Enum))) { 184 return true; 185 } 186 // A call to an interface implemented by a struct is legal whether the struct has 187 // been boxed or not. 188 if (targetType.IsInterface) { 189 foreach (Type interfaceType in instanceType.GetInterfaces()) { 190 if (AreReferenceAssignable(targetType, interfaceType)) { 191 return true; 192 } 193 } 194 } 195 } 196 return false; 197 } 198 HasIdentityPrimitiveOrNullableConversion(Type source, Type dest)199 internal static bool HasIdentityPrimitiveOrNullableConversion(Type source, Type dest) { 200 Debug.Assert(source != null && dest != null); 201 202 // Identity conversion 203 if (AreEquivalent(source, dest)) { 204 return true; 205 } 206 207 // Nullable conversions 208 if (IsNullableType(source) && AreEquivalent(dest, GetNonNullableType(source))) { 209 return true; 210 } 211 if (IsNullableType(dest) && AreEquivalent(source, GetNonNullableType(dest))) { 212 return true; 213 } 214 // Primitive runtime conversions 215 // All conversions amongst enum, bool, char, integer and float types 216 // (and their corresponding nullable types) are legal except for 217 // nonbool==>bool and nonbool==>bool? 218 // Since we have already covered bool==>bool, bool==>bool?, etc, above, 219 // we can just disallow having a bool or bool? destination type here. 220 if (IsConvertible(source) && IsConvertible(dest) && GetNonNullableType(dest) != typeof(bool)) { 221 return true; 222 } 223 return false; 224 } 225 HasReferenceConversion(Type source, Type dest)226 internal static bool HasReferenceConversion(Type source, Type dest) { 227 Debug.Assert(source != null && dest != null); 228 229 // void -> void conversion is handled elsewhere 230 // (it's an identity conversion) 231 // All other void conversions are disallowed. 232 if (source == typeof(void) || dest == typeof(void)) { 233 return false; 234 } 235 236 Type nnSourceType = TypeUtils.GetNonNullableType(source); 237 Type nnDestType = TypeUtils.GetNonNullableType(dest); 238 239 // Down conversion 240 if (nnSourceType.IsAssignableFrom(nnDestType)) { 241 return true; 242 } 243 // Up conversion 244 if (nnDestType.IsAssignableFrom(nnSourceType)) { 245 return true; 246 } 247 // Interface conversion 248 if (source.IsInterface || dest.IsInterface) { 249 return true; 250 } 251 // Variant delegate conversion 252 if (IsLegalExplicitVariantDelegateConversion(source, dest)) 253 return true; 254 255 // Object conversion 256 if (source == typeof(object) || dest == typeof(object)) { 257 return true; 258 } 259 return false; 260 } 261 IsCovariant(Type t)262 private static bool IsCovariant(Type t) 263 { 264 Debug.Assert(t != null); 265 return 0 != (t.GenericParameterAttributes & GenericParameterAttributes.Covariant); 266 } 267 IsContravariant(Type t)268 private static bool IsContravariant(Type t) 269 { 270 Debug.Assert(t != null); 271 return 0 != (t.GenericParameterAttributes & GenericParameterAttributes.Contravariant); 272 } 273 IsInvariant(Type t)274 private static bool IsInvariant(Type t) 275 { 276 Debug.Assert(t != null); 277 return 0 == (t.GenericParameterAttributes & GenericParameterAttributes.VarianceMask); 278 } 279 IsDelegate(Type t)280 private static bool IsDelegate(Type t) 281 { 282 Debug.Assert(t != null); 283 return t.IsSubclassOf(typeof(System.MulticastDelegate)); 284 } 285 IsLegalExplicitVariantDelegateConversion(Type source, Type dest)286 internal static bool IsLegalExplicitVariantDelegateConversion(Type source, Type dest) 287 { 288 Debug.Assert(source != null && dest != null); 289 290 // There *might* be a legal conversion from a generic delegate type S to generic delegate type T, 291 // provided all of the follow are true: 292 // o Both types are constructed generic types of the same generic delegate type, D<X1,... Xk>. 293 // That is, S = D<S1...>, T = D<T1...>. 294 // o If type parameter Xi is declared to be invariant then Si must be identical to Ti. 295 // o If type parameter Xi is declared to be covariant ("out") then Si must be convertible 296 // to Ti via an identify conversion, implicit reference conversion, or explicit reference conversion. 297 // o If type parameter Xi is declared to be contravariant ("in") then either Si must be identical to Ti, 298 // or Si and Ti must both be reference types. 299 300 if (!IsDelegate(source) || !IsDelegate(dest) || !source.IsGenericType || !dest.IsGenericType) 301 return false; 302 303 Type genericDelegate = source.GetGenericTypeDefinition(); 304 305 if (dest.GetGenericTypeDefinition() != genericDelegate) 306 return false; 307 308 Type[] genericParameters = genericDelegate.GetGenericArguments(); 309 Type[] sourceArguments = source.GetGenericArguments(); 310 Type[] destArguments = dest.GetGenericArguments(); 311 312 Debug.Assert(genericParameters != null); 313 Debug.Assert(sourceArguments != null); 314 Debug.Assert(destArguments != null); 315 Debug.Assert(genericParameters.Length == sourceArguments.Length); 316 Debug.Assert(genericParameters.Length == destArguments.Length); 317 318 for (int iParam = 0; iParam < genericParameters.Length; ++iParam) 319 { 320 Type sourceArgument = sourceArguments[iParam]; 321 Type destArgument = destArguments[iParam]; 322 323 Debug.Assert(sourceArgument != null && destArgument != null); 324 325 // If the arguments are identical then this one is automatically good, so skip it. 326 if (AreEquivalent(sourceArgument, destArgument)) 327 { 328 continue; 329 } 330 331 Type genericParameter = genericParameters[iParam]; 332 333 Debug.Assert(genericParameter != null); 334 335 if (IsInvariant(genericParameter)) 336 { 337 return false; 338 } 339 340 if (IsCovariant(genericParameter)) 341 { 342 if (!HasReferenceConversion(sourceArgument, destArgument)) 343 { 344 return false; 345 } 346 } 347 else if (IsContravariant(genericParameter)) 348 { 349 if (sourceArgument.IsValueType || destArgument.IsValueType) 350 { 351 return false; 352 } 353 } 354 } 355 return true; 356 } 357 IsConvertible(Type type)358 internal static bool IsConvertible(Type type) { 359 type = GetNonNullableType(type); 360 if (type.IsEnum) { 361 return true; 362 } 363 switch (Type.GetTypeCode(type)) { 364 case TypeCode.Boolean: 365 case TypeCode.Byte: 366 case TypeCode.SByte: 367 case TypeCode.Int16: 368 case TypeCode.Int32: 369 case TypeCode.Int64: 370 case TypeCode.UInt16: 371 case TypeCode.UInt32: 372 case TypeCode.UInt64: 373 case TypeCode.Single: 374 case TypeCode.Double: 375 case TypeCode.Char: 376 return true; 377 default: 378 return false; 379 } 380 } 381 HasReferenceEquality(Type left, Type right)382 internal static bool HasReferenceEquality(Type left, Type right) { 383 if (left.IsValueType || right.IsValueType) { 384 return false; 385 } 386 387 // If we have an interface and a reference type then we can do 388 // reference equality. 389 390 // If we have two reference types and one is assignable to the 391 // other then we can do reference equality. 392 393 return left.IsInterface || right.IsInterface || 394 AreReferenceAssignable(left, right) || 395 AreReferenceAssignable(right, left); 396 } 397 HasBuiltInEqualityOperator(Type left, Type right)398 internal static bool HasBuiltInEqualityOperator(Type left, Type right) { 399 // If we have an interface and a reference type then we can do 400 // reference equality. 401 if (left.IsInterface && !right.IsValueType) { 402 return true; 403 } 404 if (right.IsInterface && !left.IsValueType) { 405 return true; 406 } 407 // If we have two reference types and one is assignable to the 408 // other then we can do reference equality. 409 if (!left.IsValueType && !right.IsValueType) { 410 if (AreReferenceAssignable(left, right) || AreReferenceAssignable(right, left)) { 411 return true; 412 } 413 } 414 // Otherwise, if the types are not the same then we definitely 415 // do not have a built-in equality operator. 416 if (!AreEquivalent(left, right)) { 417 return false; 418 } 419 // We have two identical value types, modulo nullability. (If they were both the 420 // same reference type then we would have returned true earlier.) 421 Debug.Assert(left.IsValueType); 422 // Equality between struct types is only defined for numerics, bools, enums, 423 // and their nullable equivalents. 424 Type nnType = GetNonNullableType(left); 425 if (nnType == typeof(bool) || IsNumeric(nnType) || nnType.IsEnum) { 426 return true; 427 } 428 return false; 429 } 430 IsImplicitlyConvertible(Type source, Type destination)431 internal static bool IsImplicitlyConvertible(Type source, Type destination) { 432 return AreEquivalent(source, destination) || // identity conversion 433 IsImplicitNumericConversion(source, destination) || 434 IsImplicitReferenceConversion(source, destination) || 435 IsImplicitBoxingConversion(source, destination) || 436 IsImplicitNullableConversion(source, destination); 437 } 438 439 GetUserDefinedCoercionMethod(Type convertFrom, Type convertToType, bool implicitOnly)440 internal static MethodInfo GetUserDefinedCoercionMethod(Type convertFrom, Type convertToType, bool implicitOnly) { 441 // check for implicit coercions first 442 Type nnExprType = TypeUtils.GetNonNullableType(convertFrom); 443 Type nnConvType = TypeUtils.GetNonNullableType(convertToType); 444 // try exact match on types 445 MethodInfo[] eMethods = nnExprType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); 446 MethodInfo method = FindConversionOperator(eMethods, convertFrom, convertToType, implicitOnly); 447 if (method != null) { 448 return method; 449 } 450 MethodInfo[] cMethods = nnConvType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); 451 method = FindConversionOperator(cMethods, convertFrom, convertToType, implicitOnly); 452 if (method != null) { 453 return method; 454 } 455 // try lifted conversion 456 if (!TypeUtils.AreEquivalent(nnExprType, convertFrom) || 457 !TypeUtils.AreEquivalent(nnConvType, convertToType)) { 458 method = FindConversionOperator(eMethods, nnExprType, nnConvType, implicitOnly); 459 if (method == null) { 460 method = FindConversionOperator(cMethods, nnExprType, nnConvType, implicitOnly); 461 } 462 if (method != null) { 463 return method; 464 } 465 } 466 return null; 467 } 468 FindConversionOperator(MethodInfo[] methods, Type typeFrom, Type typeTo, bool implicitOnly)469 internal static MethodInfo FindConversionOperator(MethodInfo[] methods, Type typeFrom, Type typeTo, bool implicitOnly) { 470 foreach (MethodInfo mi in methods) { 471 if (mi.Name != "op_Implicit" && (implicitOnly || mi.Name != "op_Explicit")) { 472 continue; 473 } 474 if (!TypeUtils.AreEquivalent(mi.ReturnType, typeTo)) { 475 continue; 476 } 477 ParameterInfo[] pis = mi.GetParametersCached(); 478 if (!TypeUtils.AreEquivalent(pis[0].ParameterType, typeFrom)) { 479 continue; 480 } 481 return mi; 482 } 483 return null; 484 } 485 486 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] IsImplicitNumericConversion(Type source, Type destination)487 private static bool IsImplicitNumericConversion(Type source, Type destination) { 488 TypeCode tcSource = Type.GetTypeCode(source); 489 TypeCode tcDest = Type.GetTypeCode(destination); 490 491 switch (tcSource) { 492 case TypeCode.SByte: 493 switch (tcDest) { 494 case TypeCode.Int16: 495 case TypeCode.Int32: 496 case TypeCode.Int64: 497 case TypeCode.Single: 498 case TypeCode.Double: 499 case TypeCode.Decimal: 500 return true; 501 } 502 return false; 503 case TypeCode.Byte: 504 switch (tcDest) { 505 case TypeCode.Int16: 506 case TypeCode.UInt16: 507 case TypeCode.Int32: 508 case TypeCode.UInt32: 509 case TypeCode.Int64: 510 case TypeCode.UInt64: 511 case TypeCode.Single: 512 case TypeCode.Double: 513 case TypeCode.Decimal: 514 return true; 515 } 516 return false; 517 case TypeCode.Int16: 518 switch (tcDest) { 519 case TypeCode.Int32: 520 case TypeCode.Int64: 521 case TypeCode.Single: 522 case TypeCode.Double: 523 case TypeCode.Decimal: 524 return true; 525 } 526 return false; 527 case TypeCode.UInt16: 528 switch (tcDest) { 529 case TypeCode.Int32: 530 case TypeCode.UInt32: 531 case TypeCode.Int64: 532 case TypeCode.UInt64: 533 case TypeCode.Single: 534 case TypeCode.Double: 535 case TypeCode.Decimal: 536 return true; 537 } 538 return false; 539 case TypeCode.Int32: 540 switch (tcDest) { 541 case TypeCode.Int64: 542 case TypeCode.Single: 543 case TypeCode.Double: 544 case TypeCode.Decimal: 545 return true; 546 } 547 return false; 548 case TypeCode.UInt32: 549 switch (tcDest) { 550 case TypeCode.UInt32: 551 case TypeCode.UInt64: 552 case TypeCode.Single: 553 case TypeCode.Double: 554 case TypeCode.Decimal: 555 return true; 556 } 557 return false; 558 case TypeCode.Int64: 559 case TypeCode.UInt64: 560 switch (tcDest) { 561 case TypeCode.Single: 562 case TypeCode.Double: 563 case TypeCode.Decimal: 564 return true; 565 } 566 return false; 567 case TypeCode.Char: 568 switch (tcDest) { 569 case TypeCode.UInt16: 570 case TypeCode.Int32: 571 case TypeCode.UInt32: 572 case TypeCode.Int64: 573 case TypeCode.UInt64: 574 case TypeCode.Single: 575 case TypeCode.Double: 576 case TypeCode.Decimal: 577 return true; 578 } 579 return false; 580 case TypeCode.Single: 581 return (tcDest == TypeCode.Double); 582 } 583 return false; 584 } 585 IsImplicitReferenceConversion(Type source, Type destination)586 private static bool IsImplicitReferenceConversion(Type source, Type destination) { 587 return destination.IsAssignableFrom(source); 588 } 589 IsImplicitBoxingConversion(Type source, Type destination)590 private static bool IsImplicitBoxingConversion(Type source, Type destination) { 591 if (source.IsValueType && (destination == typeof(object) || destination == typeof(System.ValueType))) 592 return true; 593 if (source.IsEnum && destination == typeof(System.Enum)) 594 return true; 595 return false; 596 } 597 IsImplicitNullableConversion(Type source, Type destination)598 private static bool IsImplicitNullableConversion(Type source, Type destination) { 599 if (IsNullableType(destination)) 600 return IsImplicitlyConvertible(GetNonNullableType(source), GetNonNullableType(destination)); 601 return false; 602 } 603 IsSameOrSubclass(Type type, Type subType)604 internal static bool IsSameOrSubclass(Type type, Type subType) { 605 return AreEquivalent(type, subType) || subType.IsSubclassOf(type); 606 } 607 ValidateType(Type type)608 internal static void ValidateType(Type type) { 609 if (type.IsGenericTypeDefinition) { 610 throw Error.TypeIsGeneric(type); 611 } 612 if (type.ContainsGenericParameters) { 613 throw Error.TypeContainsGenericParameters(type); 614 } 615 } 616 617 //from TypeHelper FindGenericType(Type definition, Type type)618 internal static Type FindGenericType(Type definition, Type type) { 619 while (type != null && type != typeof(object)) { 620 if (type.IsGenericType && AreEquivalent(type.GetGenericTypeDefinition(), definition)) { 621 return type; 622 } 623 if (definition.IsInterface) { 624 foreach (Type itype in type.GetInterfaces()) { 625 Type found = FindGenericType(definition, itype); 626 if (found != null) 627 return found; 628 } 629 } 630 type = type.BaseType; 631 } 632 return null; 633 } 634 IsUnsigned(Type type)635 internal static bool IsUnsigned(Type type) { 636 type = GetNonNullableType(type); 637 switch (Type.GetTypeCode(type)) { 638 case TypeCode.Byte: 639 case TypeCode.UInt16: 640 case TypeCode.Char: 641 case TypeCode.UInt32: 642 case TypeCode.UInt64: 643 return true; 644 default: 645 return false; 646 } 647 } 648 IsFloatingPoint(Type type)649 internal static bool IsFloatingPoint(Type type) { 650 type = GetNonNullableType(type); 651 switch (Type.GetTypeCode(type)) { 652 case TypeCode.Single: 653 case TypeCode.Double: 654 return true; 655 default: 656 return false; 657 } 658 } 659 660 /// <summary> 661 /// Searches for an operator method on the type. The method must have 662 /// the specified signature, no generic arguments, and have the 663 /// SpecialName bit set. Also searches inherited operator methods. 664 /// 665 /// NOTE: This was designed to satisfy the needs of op_True and 666 /// op_False, because we have to do runtime lookup for those. It may 667 /// not work right for unary operators in general. 668 /// </summary> GetBooleanOperator(Type type, string name)669 internal static MethodInfo GetBooleanOperator(Type type, string name) { 670 do { 671 MethodInfo result = type.GetMethodValidated(name, AnyStatic, null, new Type[] { type }, null); 672 if (result != null && result.IsSpecialName && !result.ContainsGenericParameters) { 673 return result; 674 } 675 type = type.BaseType; 676 } while (type != null); 677 return null; 678 } 679 GetNonRefType(this Type type)680 internal static Type GetNonRefType(this Type type) { 681 return type.IsByRef ? type.GetElementType() : type; 682 } 683 684 private static readonly Assembly _mscorlib = typeof(object).Assembly; 685 private static readonly Assembly _systemCore = typeof(Expression).Assembly; 686 687 /// <summary> 688 /// We can cache references to types, as long as they aren't in 689 /// collectable assemblies. Unfortunately, we can't really distinguish 690 /// between different flavors of assemblies. But, we can at least 691 /// create a ---- for types in mscorlib (so we get the primitives) 692 /// and System.Core (so we find Func/Action overloads, etc). 693 /// </summary> CanCache(this Type t)694 internal static bool CanCache(this Type t) { 695 // Note: we don't have to scan base or declaring types here. 696 // There's no way for a type in mscorlib to derive from or be 697 // contained in a type from another assembly. The only thing we 698 // need to look at is the generic arguments, which are the thing 699 // that allows mscorlib types to be specialized by types in other 700 // assemblies. 701 702 var asm = t.Assembly; 703 if (asm != _mscorlib && asm != _systemCore) { 704 // Not in mscorlib or our assembly 705 return false; 706 } 707 708 if (t.IsGenericType) { 709 foreach (Type g in t.GetGenericArguments()) { 710 if (!CanCache(g)) { 711 return false; 712 } 713 } 714 } 715 716 return true; 717 } 718 } 719 } 720