Search code examples
.netreflectioncil

How to detect which .NET language is calling my code


I'm building a library that generates a user-agent string that reports some nifty data like OS version and currently installed .NET Framework versions. I'm curious:

Is it possible to detect programmatically which language is calling my library? Or is the source language completely opaque once it's compiled into CIL?


Solution

  • Edit: I turned this into a small library that encapsulates a few heuristics and makes it easy to call.

    I came up with a heuristic that seems to work well enough for my own needs.

    @Don's answer and these questions gave me some hints:

    Caveats:

    • Only differentiates between VB.NET and C#, not any other CLR languages. Assumes C# if it doesn't have enough evidence of VB.
    • It's making an educated guess, so the chance of false positives is > 0.
    • Some of the hints are based on compiler implementation details, which could change.
    • This seems to work on Mono too, but YMMV.
    • It's expensive reflection, so in real life you'd want to wrap it in a Lazy<> or some other mechanism to ensure it's only called once.
    • As @HABO mentioned, this may or may not be very useful information. I was mostly curious to see if it could be done.

    var lang = DetectAssemblyLanguage(Assembly.GetCallingAssembly());
    
    public static string DetectAssemblyLanguage(Assembly assembly)
    {
        var referencedAssemblies = assembly
            .GetReferencedAssemblies()
            .Select(x => x.Name);
    
        var types = assembly
            .GetTypes();
    
        // Biggest hint: almost all VB.NET projects have a
        // hidden reference to the Microsoft.VisualBasic assembly
        bool referenceToMSVB = referencedAssemblies.Contains("Microsoft.VisualBasic");
    
        // VB.NET projects also typically reference the special
        // (YourProject).My.My* types that VB generates
        bool areMyTypesPresent = types.Select(x => x.FullName).Where(x => x.Contains(".My.My")).Any();
    
        // If a VB.NET project uses any anonymous types,
        // the compiler names them like VB$AnonymousType_0`1
        bool generatedVbNames = types.Select(x => x.Name).Where(x => x.StartsWith("VB$")).Any();
    
        // If a C# project uses dynamic, it'll have a reference to Microsoft.CSharp
        bool referenceToMSCS = referencedAssemblies.Contains("Microsoft.CSharp");
    
        // If a C# project uses any anonymous types,
        // the compiler names them like <>f__AnonymousType0`1
        bool generatedCsNames = types.Select(x => x.Name).Where(x => x.StartsWith("<>")).Any();
    
        var evidenceForVb = new bool[] 
        {
            referenceToMSVB,
            myTypesPresent,
            vbGeneratedNames
        };
    
        var evidenceForCsharp = new bool[] {
            true, // freebie. ensures ties go to C#
            referenceToMSCS,
            csGeneratedNames
        };
    
        var scoreForVb = evidenceForVb.Count(x => x)
                         - evidenceForCsharp.Count(x => x);
    
        // In the case of a tie, C# is assumed
        return scoreForVb > 0
            ? "vb"
            : "cs";
    }