Add UserData: Object Reference not set to the instance of an object

@sonderskovmathias,

I don’t where you ended up this, but I came back around to this post as I was thinking about adding UserData to InstanceDefinitions.

The way we’ve been successful with UserData in unit tests is by declaring callback functions for get/add/remove operations as well as the interface for your userdata in a shared library. Then you implement those callbacks as well as the UserData class in the plugin project. In your plugin’s constructor you then assign the implementation of get/add/remove callbacks to the delegates in your shared library. Finally you need to make sure that your plugin is loaded as a part of the unit tests. This makes is possible to invoke the callback functions from your unit testing code without needed a direct dependency on the plugin *.rhp. I leveraged the Rhino.Testing library to do this. To expand upon my example above:

#In Shared.Dll

public static class UserDataExtensions
{
    public delegate bool TryGetUserDataDelegate(RhinoObject ro,
            [MaybeNullWhen(false)] out ICustomUserData userData);

    public Func<ICustomUserData, RhinoObject>? AddUserDataFunc {get; set;}
    public Func<bool, RhinoObject>? RemoveUserDataFunc {get; set;}
    public TryGetUserDataDelegate? TryGetUserDataFunc {get; set;}

    public ICustomUserData AddUserData(this RhinoObject ro)
    {
        return AddUserDataFunc?.Invoke(ro);
    }

    public bool TryGetUserData(this RhinoObject ro,
            [MaybeNullWhen(false)] out ICustomUserData userData) 
    {
        return TryGetUserDataFunc?.Invoke(ro, out userData) ?? false;
    }

    public bool RemoveUserData(this RhinoObject ro)
    {
        return RemoveUserDataFunc?.Invoke(ro) ?? false;
    }
}
#in CustomUserDataImpl.cs

public static ICustomUserData AddUserDataImpl(RhinoObject ro)
{
    if(TryGetUserDataImpl(ro, out var data) return data;
    var attributes = ro.Attributes.Duplicate();
    var data = new CustomUserDataImpl();
    attributes.UserData.Add(data);
    ro.Document.Objects.ModifyAttributes(ro.Id, attributes, false);
}

public static bool TryGetUserDataImpl (RhinoObject ro, out ICustomUserData rhinoData)
{
    var cdata = ro.attributes.UserData.Find(typeof(CustomUserDataImpl));
    rhinoData = (ICustomUserData)cdata;
    return rhinoData is not null;
}

public static bool RemoveUserDataImpl(RhinoObject ro)
{
    var userObject = ro.attributes.UserData.Find(typeof(CustomUserDataImpl));
    if (userObject is null) return false;
    var attributes = ro.Attributes.Duplicate();
    attributes.UserData.Remove(userObject);
    ro.Document.Objects.ModifyAttributes(ro.Id, attributes, false);
    return true;
}
#in Plugin.cs
public class CustomPlugIn : Rhino.PlugIns.PlugIn
{
    protected override LoadReturnCode OnLoad(ref string errorMessage)
    {
        UserDataExtensions.AddUserDataFunc = AddUserDataImpl;
        UserDataExtensions.TryGetUserDataFunc = TryGetUserDataImpl;
        UserDataExtensions.RemoveUserDataFunc = RemoveUserDataImpl;

       // Any other setup implementation for your plugin
    }
}

All of that said, I’ve also done the UserDictionary approach and it’s probably good enough for most situations. I moved away from using ArchivableDictionaries because I wanted to have more control over serialization and copy logic

There used to be some gotcha’s around AppDomains with the Visual Studio/JetBrains Tests runners, but those don’t appear to be an issue anymore with .Net8 and Rhino8. Hope you’ve settled your own approach to handle this problem in the meantime.

1 Like

That’s a creative hack, i like it! We settled for the userdictionary for now but bookmarking this one :slight_smile: Thanks for sharing