Search code examples
c#derived-classcreateinstance

How to unwrap object handle created with Activator.CreateInstance to the base class?


In my previous question I wanted to force all derived classes to implement their own options class. The given answer provided a solution to my problem but then created another problem as I couldn't unwrap the object handle anymore.

I have created an empty public interface and a class that implements it:

public interface IOptions{};

public class SearchOptions : IOptions
{
    public Boolean SearchHiddenFiles { get; set; }
    public Boolean CaseSensitive { get; set; }
}

Then I created a base class with an abstract parameter TaskOptions which is type of IOptions:

public abstract class Task<T> where T : IOptions
{
  public string ID {get; set;}
  public string Name {get; set;}

  public abstract T TaskOptions { get; set; }
  public abstract void Execute();
}

And finally, I created bunch of classes which extend Task class, i.e.:

public class TaskSearch : Task <SearchOptions>
{
    public override SearchOptions TaskOptions { get; set; }
    public override void Execute()
    {
        Console.Write("Search running");
    }
}

The main program initializes a Job object which takes a file that has json notation that defines the id, name, etc.. along with tasks.

Lastly, here is the job class:

public class Job
{
    public String Name { get; set; }
    public String Description { get; set; }
    public List<Task<IParameters>> Tasks { get; set; }
    public Job(string JsonFile)
    {        
        using (StreamReader file = File.OpenText(JsonFile))
        {
            string json = file.ReadToEnd();
            JObject o = JObject.Parse(json);

            this.Name = o.SelectToken("Name").ToString();
            this.Description = o.SelectToken("Description").ToString();

            JArray arr = (JArray)o["Tasks"];

            foreach (var t in arr)
            {
                var taskclass = t["Name"].ToString();

                var taskobj = Activator.CreateInstance("RunTasks", "RunTasks." + taskclass);

                var task = (Task<IParameters>)taskobj.Unwrap(); //<-- throws System.InvalidCastException because taskobj is TaskSearch
                // set task.ID, task.Name, etc..
                //...
                //...
                Tasks.Add(task);
            }
        }
    }
}

Before I changed the Task class to implement IOptions interface, I unwrapped it as:

var task = (Task)taskobj.Unwrap();

which worked but after adding IOptions and casting it to Task<IOptions> it throws System.InvalidCastException error because taskobj is TaskSearch (not Task).

If I manually type Task<SearchOptions> then it unwraps but then complains about the Tasks list at the end as it's now not type of Task<IOptions>. I need to make the Tasks list generic so it accepts all derived classes and find a way to get type parameters before unwrapping taskobj or get it from taskclass string which holds the current task name.

I'll create a dictionary for the options if I have to but this is how I wanted to implement it and I'd like to know if I can.

Thank you


Solution

  • Instead of implicitly defining task object, I defined it explicitly as dynamic type.

    dynamic task = taskobj.Unwrap();
    

    Without casting, compiler does not complain about it because it's dynamic and it knows it's TaskSearch during runtime.