如何确定System.Type是自定义类型还是框架类型?

14

我希望能够清楚地确定我所拥有的类型是自定义类类型(MyClass)还是框架(System.String)提供的类型。

在反射中是否有办法将我的类类型与系统字符串或其他由框架提供的类型区分开来?

6个回答

7

安全地检查一个类型是否是一个程序集的一部分的唯一方法是检查该程序集的完全限定名称,其中包含其名称、版本、文化和公钥(如果已签名)。所有的 .Net 基类库(BCL)都使用微软的私钥进行签名。这使得其他任何人几乎不可能创建一个与基类库具有相同完全限定名称的程序集。

//add more .Net BCL names as necessary
var systemNames = new HashSet<string>
{
"mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
};

var isSystemType = systemNames.Contains(objToTest.GetType().Assembly.FullName); 

一种稍微不那么脆弱的解决方案是使用AssemblyName类并跳过版本号/区域设置检查。当然,这假设公钥在版本之间不会更改。

//add more .Net BCL names as necessary
var systemNames = new List<AssemblyName>
{
new AssemblyName ("mscorlib, Version=4.0.0.0, Culture=neutral, " +
                  "PublicKeyToken=b77a5c561934e089"),
new AssemblyName ("System.Core, Version=4.0.0.0, Culture=neutral, "+
                  "PublicKeyToken=b77a5c561934e089")
};

var obj = GetObjectToTest();

var objAN = new AssemblyName(obj.GetType().Assembly.FullName);

bool isSystemType = systemNames.Any(
        n =>  n.Name == objAN.Name 
           && n.GetPublicKeyToken().SequenceEqual(objAN.GetPublicKeyToken()));

大部分的BCL都使用相同的密钥进行签名,但不是全部。您可以使用AssemblyName类来仅检查公钥标记。这取决于您的需求。


6

如果你只是想区分MyClassstring,那么你可以直接检查这些类型:

Type typeToTest = GetTypeFromSomewhere();

if (typeToTest == typeof(MyClass))
    MyClassAction();
else if (typeToTest == typeof(string))
    StringAction();
else
    NotMyClassOrString();

如果你需要更一般性的检查给定类型是否为框架类型,那么你可以检查它是否属于 System 命名空间。
// create an array of the various public key tokens used by system assemblies
byte[][] systemTokens =
    {
        typeof(System.Object)
            .Assembly.GetName().GetPublicKeyToken(),  // B7 7A 5C 56 19 34 E0 89
        typeof(System.Web.HttpRequest)
            .Assembly.GetName().GetPublicKeyToken(),  // B0 3F 5F 7F 11 D5 0A 3A 
        typeof(System.Workflow.Runtime.WorkflowStatus)
            .Assembly.GetName().GetPublicKeyToken()   // 31 BF 38 56 AD 36 4E 35 
    };

Type typeToTest = GetTypeFromSomewhere();

string ns = typeToTest.Namespace;
byte[] token = typeToTest.Assembly.GetName().GetPublicKeyToken();

bool isSystemType = ((ns == "System") || ns.StartsWith("System."))
                    && systemTokens.Any(t => t.SequenceEqual(token));

并不是真正的防弹,例如如果命名空间被称为 "SystemUtil" ;) - Thomas Kjørnes
@thomas:我发帖后很快意识到了这个问题,但在我编辑之前被叫走了。现在已经修复了。 - LukeH
我非常确信您实际上可以编写自己的汇编语言并创建System.[something]命名空间... 尽管我不是很喜欢这样做,但我知道一些开源项目这样做了--例如System.Data.SQLiteSystem.Drawing.Html - Dan Tao
@Dan:这是非常正确的,尽管这也是一种相当糟糕的做法。我已经编辑了代码,包括针对程序集的PublicKeyToken的检查,以确保它是真正的系统类型。当然,如果你只想检查它是否为核心系统类型,那么你可以只做if (typeToTest.Assembly == typeof(int).Assembly),但这会排除更广泛的System.*命名空间中的成员(例如,XmlDocument)。我想这真的取决于OP究竟想要实现什么。 - LukeH

4
您可以检查声明该类型的程序集。
object.GetType().Assembly

3
那么问题是:如何确定一个程序集是 .NET Framework 的一部分? - Hans Passant

1

检查程序集是否属于CLR库:

myType.Module.ScopeName == "CommonLanguageRuntimeLibrary"

如下所述:

如此解释这里


0

如果有帮助的话,这是我的解决方案:

private static readonly string _RuntimeDirectory =
    System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
public static bool IsSystem(this Type type)
    => type.Assembly.Location.StartsWith(_RuntimeDirectory);

0

并非所有的框架类都在 System 命名空间中(它们也可以是 Microsoft 等)。

因此,您可以将已知的框架类的位置与您正在测试的类型的位置进行比较,例如:

  if (String.CompareOrdinal(
        Path.GetDirectoryName(typeof(String).Assembly.Location), 
        Path.GetDirectoryName(typeof(MyType).Assembly.Location)
      ) == 0)
  {
    //Framework Type
  }
  else
  {
    //3rd Party DLL
  }

这不是最好的解决方案,但比仅测试命名空间是否以System开头更安全(我可以创建一个以System开头但不是框架类的命名空间)。

编辑

此外,除了上述测试之外,验证类型是否从全局程序集缓存加载也是有必要的:

typeof(MyType).Assembly.GlobalAssemblyCache

2
据我所知,BCL仅包含System.*命名空间。Microsoft.*命名空间是BCL的超集,有时被称为FCL,并不属于ECMA CLI标准。http://en.wikipedia.org/wiki/Base_Class_Library - LukeH
@LukeH:非常好的观点。我想要表达的重点是不要依赖以System开头的命名空间。正如其他解决方案所示,明确是最好的选择;这是一种“穷人”的检查方法,我想提出来以防万一。 - Chris Baxter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接