Bobson
Bobson

Reputation: 13706

Lazy<T> and reflection-based initalization

I have a series of classes which initialize themselves when created based on using reflection to read a custom attribute on each property/field. The logic for all that is contained in an Initialize() method which they all call, which exists on the base class they inherit from.

I want to add usages of Lazy<T> to these classes, but I don't want to specify the function(s) in the constructor for each class, because they are "thin" constructors and the heavy lifting is in Initialize(). Conversely, I want to keep type-safety and such so I can't just provide a string of the code to use to initialize the Lazy<T>. The problem is that any usage which refers to the specific properties of the object can't be used in a static context.

Specifically, this is what I want my code to look like in an ideal world:

public class Data : Base
{
    public Data(int ID) { Initalize(ID); }

    [DataAttr("catId")] // This tells reflection how to initialize this field.
    private int categoryID;

    [LazyDataAttr((Data d) => new Category(d.categoryID))] // This would tell reflection how to create the Lazy<T> signature
    private Lazy<Category> _category;
    public Category Category { get { return _category.Value; } }
}
public abstract class Base
{
    protected void Initalize(int ID) 
    { 
        // Use reflection to look up `ID` and populate all the fields correctly.
        // This is where `categoryID` gets its initial value.
        // *** This is where _category should be assigned the correct function to use ***
    }
}

I would then access this the same way I would if Category were an automatic property (or an explicitly lazy loaded one with an _category == null check)

var data = new Data();
var cat = data.Category;

Is there any way I can pass the type information so that the compiler can check that new category(d.categoryID) is a valid function? It doesn't have to be via an Attribute, but it needs to be something I can see via Reflection and plug in to anything that has a Lazy<T> signature.


As an alternative, I will accept a way to do

 private Lazy<Category> _category = (Data d) => new Category(d.categoryID);

This could either avoid reflection altogether, or use it to transform from this form to a form that Lazy<T> can handle.

Upvotes: 0

Views: 1043

Answers (1)

Bobson
Bobson

Reputation: 13706

I ended up using a solution inspired by @Servy's suggestion to get this working. The base class's Initialize() method now ends with:

protected void Initialize()
{
    // Rest of code...

    InitializeLazyVars();
    /* We need do nothing here because instantiating the class object already set up default values. */
    foreach (var fi in GetLazyFields())
    {
        if (fi.GetValue(this) == null)
            throw new NotImplementedException("No initialization found for Lazy<T> " + fi.Name + " in class " + this.GetType());
    }
}

InitializeLazyVars() is a virtual method that does nothing in the base class, but will need to be overridden in the child classes. If someone introduces a new Lazy<T> and doesn't add it to that method, we'll generate an exception any time we try to initialize the class, which means we'll catch it quickly. And there's only one place they need to be added, no matter how many constructors there are.

Upvotes: 1

Related Questions