1

Basically what I want lies somewhere between FormatterServices.GetUninitializedObject() and Activator.CreateInstance().

This is for a plugin type of system so the type is variable, but I dont imagine that is an issue as both the above handle variable types just fine.

Basically I want to create an instance of the plugin's class, initialize all the fields and properties with their defined values but hold off on calling the constructor until my parent application sees if it has any configured settings it wants to inject in.

IInputDeviceEnumerator newEnum = (IInputDeviceEnumerator)FormatterServices.GetUninitializedObject(type);

if (newEnum is IIMConfigurable typeSettings)
{

    string pluginDirectory = Path.GetDirectoryName(new System.Uri(typeSettings.GetType().Assembly.CodeBase).AbsolutePath);
    string pluginConfigPath = Path.Combine(pluginDirectory, "settings.json");

    if (File.Exists(pluginConfigPath))
    {
        try
        {
            JsonConvert.PopulateObject(File.ReadAllText(pluginConfigPath), typeSettings.config);
        }
        catch { }
    }

    SharedProperties.Settings.pluginSettings.settingsGroups.Add(typeSettings.config);
}
var constructor = type.GetConstructor(Type.EmptyTypes);
constructor.Invoke(newEnum, null);

I have a feeling the answer lies somewhere in PopulateObjectMembers, but I have not found enough info on it yet to decide.

Wobbles
  • 3,033
  • 1
  • 25
  • 51
  • What is problem with your code? – Pavel Anikhouski Nov 23 '19 at 14:09
  • @PavelAnikhouski This does not initialize fields or properties, they are all null or default value. If the class has `bool myVal {get;set;} = true;` I want the code above to see that assigned value. (rough simplified example, my types defaults are somewhat more complex) – Wobbles Nov 23 '19 at 14:11
  • Expecting an object to behave normally without calling the constructor is very fragile, I'd say. Why do you want to set the properties early, but not call the constructor? I'd either construct the full object earlier, or hold off on setting the properties until you know that you need an instance. – Jon Skeet Nov 23 '19 at 14:28
  • @JonSkeet for the reasons listed above. The plugins execution is typically kicked off by their constructor and I dont want them starting until the main app has loaded it's relevant info. I could add a `init()` method to my interface for this, but was hoping to avoid that. I dont really need the object to "behave" normally, just need the fields to be populated as the constructor is called soon after. – Wobbles Nov 23 '19 at 14:36
  • @Wobbles: Adding an initialization method is almost certainly a better way of doing this. The idea of calling a constructor *after* setting fields etc really goes against the grain of C#, and constructors typically *don't* start off significant execution flows. Alternatively, separate each plugin into its configuration (which is initialized early) and "the plugin itself" which you create when you expect it to start executing. But a `Start` method really does sound like the most appropriate approach here. You're fighting against all the normal expectations of C# in your current idea. – Jon Skeet Nov 23 '19 at 14:39
  • @JonSkeet, Yea, it's looking that way, I was just hoping to avoid issues by plugins that dont follow this practice properly and have the constructors executing the bulk of their code. I really have very little control over the plugins as they are 3rd party. – Wobbles Nov 23 '19 at 14:41
  • @Wobbles: If you don't have much control over the plugins that's even *more* of a reason not do very strange things. I think the requirement of "don't start extra work in a constructor" is *far* less odd than the requirement of "don't rely on a constructor having been called at all" which your approach needs. Note that if there's some application context that plugins will need in order to execute, then if you only pass that into the `Start` method (or whatever) it'll make it a lot less tempting for plugins to do the wrong thing. – Jon Skeet Nov 23 '19 at 14:43
  • @JonSkeet I also dont want to pass anything per-say, this is why I have a seperate interface for plugins that are configurable and my main plugin loader injects the stored settings into the property at load-time rather than passing it as a param assuming the plugin will treat it correctly and implement the proper INotify etc so the changes reflect back to the main app etc etc. – Wobbles Nov 23 '19 at 14:48
  • @Wobbles: I'm afraid at this point it sounds like there's so much context that we don't know about that I can't help you further. I remain convinced that trying to break all the normal rules of how objects are initialized is a really bad idea that will cause both you and plugin developers pain later on. But if you remain *unconvinced* of that, I don't think I'll be able to provide any more useful input. – Jon Skeet Nov 23 '19 at 14:51
  • @JonSkeet I will likely go with the `CreateInstance()`, then populate my settings, then call a `init()` as it seems the safest way of doing things without relying on the plugin too much. – Wobbles Nov 23 '19 at 14:53

2 Answers2

0

As far as I know there are no analogues to C++'s placement new that combined with FormatterServices.GetUninitializedObject would have probably solved your problem.

But I believe that you are trying to do is a step in a wrong direction - trying to fight constructor invariants/work on uninitialized objects is usually a bad idea.

Unless this is some already active and (relatively) widely used plugin system and you have no other choice, I'd recommend you to redesign it using one of the more standard and language/platform-independent approaches:

  1. Either pass all parameters to the constructor as some Dictionary/dynamic.
  2. Or do the same with some IPluginFactory.Create.
  3. Or have an Initialize method that accepts the same Dictionary/dynamic and initializes the plugin after it had been constructed.

Personally, I would have gone with constructor accepting Dictionary/dynamic.

Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
  • I think you misunderstand what I am trying to do, or at the very least it seems that your way makes too many assumptions about other aspects of the application. – Wobbles Nov 23 '19 at 14:39
  • 1
    @Wobbles: Then you need to give us more information. Currently, all you've told us is that you really *want* to do something in a way that violates the normal way that .NET works. You haven't told us *why* you don't want to do things in a more regular way. All we can really say is "No, the way you're trying to do things won't work" and suggest alternatives as this answer does. – Jon Skeet Nov 23 '19 at 14:40
  • @JonSkeet `PopulateObjectMembers` not a viable solution if sticking inside the guidelines of what I am trying to accomplish? – Wobbles Nov 23 '19 at 14:44
  • @Wobbles: Not if you expect to be able to call a constructor on the same instance *afterwards*. At the very least, that will give *really* confusing semantics that I suspect will cause all manner of problems in plugins. I would very strongly advise you to look for approaches that *don't* violate normal expectations of the .NET type system - and this answer gives several ideas about how you might do that. – Jon Skeet Nov 23 '19 at 14:47
0

I appreciate the the help and guidance on this one, but for my final solution, I decided to think a bit outside the box.

Since this was for a plugin system that had both internally configurable and externally overridable settings I wanted to settings to be accessible even if the plugin was not full initialized.

My solution was to create an attribute on the plugin type that was a pointer to another type that housed the settings. Like I said, outside the box.

This allowed me to see the attribute and defined settings class, create an instance of the settings class and populate it all before I had begun initializing the plugin. (this also had the benefit of being able to modify plugin settings without the plugin being enabled at all)

then in my plugin wrapper in the instance creation code, I detected if there is a constructor on the plugin that accepts the same type back and uses that otherwise it uses the default.

Wobbles
  • 3,033
  • 1
  • 25
  • 51