.NET Native Deep Dive: Making Your Library Great

.NET Native Deep Dive: Making Your Library Great

Rate This
  • Comments 15

This post was authored by Morgan Brown, a Software Development Engineer on the .NET Native team. It is the fourth post in a series of five about Runtime Directives. Please read the first post in this series, Dynamic Functionality in Static Code, before reading this post.

As a .NET library author, you don’t necessarily have to do anything to have your library work with .NET Native. That said, your library might use some patterns that create extra burdens on app developers using your library. Let’s talk about an example and how to make it better. If you haven’t worked with Runtime Directives before, take a quick detour to Dynamic Features in Static Code for an introduction.

Let’s say I’m working on a simple reflection-based JSON-like serializer with the code below:

class Serializer
{
  public static string Serialize(Object objectToSerialize)
  {
    StringBuilder serializedString = new StringBuilder();

    // To do this, I need metadata for the type of objectToSerialize
    TypeInfo typeInfo = objectToSerialize.GetType().GetTypeInfo();
    serializedString.AppendLine("{");

    // To do this, I need to ensure all of the fields in objectToSerialize
    // aren't removed by optimizations
    foreach (FieldInfo field in typeInfo.DeclaredFields)
    {
      serializedString.AppendFormat("\"{0}\": ", field.Name);
      Type fieldType = field.FieldType;
      TypeInfo fieldTypeInfo = field.FieldType.GetTypeInfo();
      if (fieldTypeInfo.IsPrimitive)
      {
        object fieldValue = field.GetValue(objectToSerialize);
        serializedString.AppendLine(fieldValue.ToString());
      }
      else if (fieldType == typeof(string))
      {
        object fieldValue = field.GetValue(objectToSerialize);
        serializedString.AppendFormat("\"{0}\"\n", (string)fieldValue);
      }
      else
      {
        // This is recursive, so I need metadata for the field types
        // (and their types etc.).
        string serializedField = Serialize(field.GetValue(objectToSerialize));
        serializedString.AppendLine(serializedField);
      }
    }
    serializedString.AppendLine("}");
    return serializedString.ToString();
  }
}

In a dynamically compiled environment, that code would work on any user type. With static compilation, there’s a tricky situation for app developers. They don’t know how my library works, but when they try to run their app, they might get MissingMetadataExceptions. Then they have to guess what I might reflect over. Of course, I don’t know what apps might use my library and what classes they might try to serialize. There are a few solutions to these problems.

No Code Change

You might want to support versions of your library that are already out in the wild. In that case the simplest change you can make is to write an rd.xml file for your library. They have the same syntax as rd.xml files written by app authors and the compiler just merges your directives with the app’s rd.xml. Start off by creating a file ending with .rd.xml and paste the XML below. You can just distribute it to your users to include in their apps if you’re not shipping a new version of your library. If you are shipping a new version, you can set the build action for the rd.xml file in your project to “Embedded resource” and the file will get included in your library so that the compiler can always find it and app developers can’t accidentally lose it.

<?xml version="1.0" encoding="utf-8"?>
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
  <Library Name="MyLibrary">
    <!-- Add your library directives here -->
  </Library>
</Directives>

If there are some types you know you need to reflect over, you can add them there in the same way as an app author would. The more interesting case is covering types that you don’t know about yet. There are three types of directives that help you do that:

  1. GenericParameter: This applies the directive to any type a particular generic class or method gets instantiated over. For example, a library defines MyLibraryGeneric and an app uses MyLibraryGeneric and MyLibraryGeneric, a GenericParameter directive like this would apply to MyAppType1 and MyAppType2:

    <Type Name="MyLibraryGeneric{T}">
      <GenericParameter Name="T" Dynamic="Required All" />
    </Type>
  2. TypeParameter: This applies a directive to types that get passed to your method as a System.Type. So to make a method like this work:

    static string GetTypeName(Type typeToReflect)
    {
      return typeToReflect.GetTypeInfo().Name;
    }

    You can use a directive like this.

    <Type Name="MyLibraryClass">
      <Method Name="GetTypeName">
        <TypeParameter Name="typeToReflect" Browse="Public" />
      </Method>
    </Type>
  3. Parameter: This applies a directive to the type of the object passed to your method. For example, in the case of the serializer example above, the method is declared to take a parameter of type System.Object, but that’s not that helpful since it could be anything. If I do something like:

    Serializer.Serialize(new MyClass()); 

    Parameter means the directive will apply to MyClass. This is a good choice for cases where we don’t know what type the app developer will pass in, but the compiler might be able to work it out. The RD.xml for that would look like:

    <Type Name="Serializer">
      <Method Name="Serialize">
        <Parameter Name="objectToSerialize" Serialize="Required All" />
      </Method>
    </Type>

TypeParameter and Parameter sound like nice solutions and they can certainly help, but while the .NET Native compiler can understand some patterns, it can’t analyze all of them (that said, we’re working hard at improving that analysis as well). As a result, they won’t necessarily be able to identify all types that could get passed to your method. GenericParameter is different – it is possible to identify all generic instantiations, so you can count on that working perfectly every time.

Easy Code Changes

If you’re willing and able to change your code a bit, there are some changes you can make that will make reflection work much more often.

  1. Make your APIs generic. In the serializer example, if Serialize were a generic method, we could use a GenericParameter directive with it instead of a Parameter directive and it would work every single time. For C# developers, this usually isn’t a disruptive change since the compiler will automatically fill in the generic parameter. So instead of

    public static string Serialize(Object objectToSerialize) 

    use:

    public static string Serialize<T>(T objectToSerialize) 
  2. Avoid weakly typed wrappers. If you wrap reflection methods or methods that you’ve written directives for in methods that don’t have directives, the compiler may not be able to see through the wrapper to what the type really is. For example, if I wanted to wrap Serialize with a method that checks for null, but don’t make that method generic like this:

    public static string SerializeWithNullCheck(object objectToSerialize)
    {
      if(objectToSerialize == null)
      {
        return String.Empty;
      }
      Serialize<object>(objectToSerialize);
    }

    This obfuscates what the type we’re really trying to serialize is so that the compiler might not be able to find it.

  3. Use new reflection and the generic marshal APIs (details on reflection in Evolving the Reflection API). We’ve written directives for the new reflection and System.Runtime.InteropServices.Marshal APIs. If you use those, we can automatically pick up on some of your patterns.

Bigger Changes for Great Results

If you have the time to make some bigger changes to the internals of your library, you can use patterns that will work every time and will even make your library faster with .NET Native.

  1. Eliminate unnecessary reflection. We sometimes see cases where libraries use reflection, but it’s not necessary to accomplish the task. For example, some exception loggers print an exception’s type name and message separately, which requires reflection. Using ToString() prints the same information, but doesn’t require reflection. Similarly, there’s a common trick for automatically getting names for INotifyPropertyChanged using LINQ Expressions and reflection, but you can use the CallerMemberName attribute to have the compiler automatically generate it instead.
  2. Without .NET Native, compiled LINQ Expressions are sometimes used as a performance optimization. In .NET Native, those Expressions get interpreted instead of compiled. So, they actually run more slowly, as well as potentially using reflection on types that are harder for app developers to predict. Think about using generics or other statically written code to get better performance that works without RD.xml changes.

We’re excited to have lots of libraries that are even better with .NET Native. We’d love to hear your feedback. You can leave comments on this post or send mail to dotnetnative@microsoft.com.

Leave a Comment
  • Please add 6 and 6 and type the answer here:
  • Post
  • While I'm excited to see this functionality and what it can do for the app store apps I'm not building, I think this would help the desktop/server apps world a huge huge amount. Any idea what kinda time frames we might expect to see this functionality for the .net server apps I build?

  • @Weston: We're focusing right now on Store apps but we realize that a lot of what we're doing can be brought to desktop and server scenarios. Unfortunately, we're not commenting on time frames right now.

  • It's great but I need it more to WPF and WinForms, I have some pretty heavy computations apps, which could really use this performance boost + I heard .NET Native it's great way to obfuscate your code

  • @Pawel: Desktop apps are definitely on our radar but for now we're focused on Store apps.

    .NET Native obfuscates your code as much as an optimized C++ app is obfuscated. It's possible, but very difficult, to reverse-engineer optimized C++ code.

  • Is there any support in .Net Native for marking types that require metadata based upon an attribute? I'm specifically thinking of MEF where you flag types for dependency injection by applying an [Export] attribute to the class - in this case it would be useful if .Net Native would know to generate metadata so MEF can compose the dependency tree at runtime.

  • Everything about .NET native looks exciting and promising to me until I see "compiled LINQ expressions get interpreted instead of compiled". Unfortunately this is very common practice for high performance serialization-related libraries. It is hard for us to work around the on-the-fly compilation technique by 'using generics or other statically written code'. More hint on this area? Thanks.

  • I'd like the team to elaborate on the challenges of Linq and give specific guidance with examples.

  • I love the overall idea, but at this stage, I am not convinced .Net native has the level of appeal or value proposition .Net clr bundles, apart in relatively specific phone/embedded or cloud scenarios. In the general development cases (that don't necessarily account for the majority of usage nowadays, bit still), having to choose between a 20 something performance gain or waiting for some supporting tooling (why oh why doesn't the exception produce the directive to correct itself?) or the next CPU price drop, one of the later will prevail. For general/enterprise development, the value brought by the facilities and maturity of the clr could be hard to match.

    Although too early to tell, an interesting possibility for the laydev I've become would be to have the possibility to leverage optimized .Net native libraries from my layman's code and derive some performance benefits without incurring any of the constraints that made me switch from c++/com the first place :)

  • @Andy Wilkinson: We went through a number of redesigns with rd.xml before settling on this. We considered attributes but moved away from them. One distinct disadvantage of attributes is that the set metadata needed from a particular piece of code can change based on the program where the code is used. Thus we wanted the directives to flow with the program (or library) instead of the code snippet.

  • @Karl: Compiling LINQ expressions at runtime is impossible in the fully static design we're using right now. The interpreter is actually faster in our tests until you cross a threshold--after n calls to the LINQ expression you'd have done better with it being compiled but below n calls interpreting is actually faster.

    Feel free to shoot us mail if you want to dive deeply into your scenarios. We'd love to have a discussion.  

  • Alayman: Thanks for the feedback. We're making investments in many areas, including supporting tooling to help .NET Native programs not need rd.xml files at all. As for CPU price drops, our friends at Intel, AMD and all the ARM silicon vendors are hard at work, I assure you!

  • @Karl - to back up what Andrew is pointing out, it turns out that the overhead of firing up the JIT compiler to emit native code for a LINQ expression is rather high, so we actually measure a rather large number of executions of a query being faster in the interpreted mode than in a fully JITed mode.

  • @Alayman - it turns out that MissingMetadataExceptions do include the line of rd.xml you would need to solve the individual problem that is causing the exception.  You can make progress by just including these lines as exceptions occur, although this is a bit of a game of whack-a-mole.  This series of blog posts shows some more advanced techniques to getting things working more easily.   In this particular blog post, the discussion is around how library authors can make it such that app authors don't need rd.xml to interact with their libraries.  Previous blog posts were about a faster way to solve your missing metadata problems.

    It's been mentioned a few times during the posts, but another point that I want to emphasize is that our goal is that application authors should eventually not have to think about rd.xml files.  (or, as people around these halls have heard me say numerous times, the amount of time app authors need to think about them asymptotically approaches zero as time approaches infinity :-)  For sure we have work to go to reach that goal, so a lot of the information here will be useful to people using our developer previews to get themselves unblocked.  In places where people are hitting a missing metadata exception, we would love to hear about it to see if there is some way we can make our tools better so that the particular case better in the future.

  • Hey y'all~ just one quick question, will the "Parameter" directive support params calls propertly? I.e. extending the trivial example imagine:

    public static string Serialize(params object[] objectsToSerialize)

    As a user I'd expect a parameter directive for "objectsToSerialize" to apply to the elements of the params array, but from an implementation standpoint I can imagine some challenges in the implementation.

    The actual method I would need this for is the following one: github.com/.../ILogger.cs

    Cheers,

    Nick

  • @Nick - we currently don't have a way to see through params arrays, however that's a really good idea and your motivating logger example makes a lot of sense.  I've filed a task to our backlog to investigate supporting params arrays.   No promises, but I do agree that it does seem like a useful feature for us to support.

Page 1 of 1 (15 items)