.NET Native Deep Dive: Making Your Library Great

.NET Team

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.

0 comments

Discussion is closed.

Feedback usabilla icon