snappymcsnap
snappymcsnap

Reputation: 2103

Generics class with generic properties?

I have a scenario where I have a bunch of jobs that I am scheduling to run at various times, the jobs themselves are being handled generically already which is great. And I have an abstract BaseJob class that they all inherit from that I use for common things (like the jobPK, startTime, exception logging, reporting, etc). But beyond that the jobs are very different, they have different properties and data associated with them that is entriely specific to them (I call these proprties JobDetails). So for example:

JobDetails for Job1
-customerId int
-cost double
-sku string
-userDefinedProperties SomeCustomObjectType

JobDetails for Job2
-name string
-executionDate DateTime
-otherProperties SomeOtherCustomObjectType

In the base class I would like to be able to store a reference to these JobDetails in as generic a fashion as possible (so in other words I don't want to just store it as object) to minimize the overhead for boxing/unboxing. Then I want to have the BaseJob class handle a lot of the common functionality that is needed for the app, so for example, if a job fails, I want to save its JobDetails to the database so that it can be restarted, I also want to log any errors that may have occured to a given job. For this I need to be able to extract those JobDetails and make use of them.

It seems like I need to make use of .NET generics and have a class of generic properties that I can stuff anything into and not have to worry about typing. What's the best way to handle this and make it efficient and flexible?

I hope that is clear, thanks for the help

Upvotes: 1

Views: 574

Answers (2)

Joel Day
Joel Day

Reputation: 1956

How about something like...

public abstract class JobBase<TDetails>
{
    private TDetails details;
    protected TDetails Details
    {
        get
        {
            if (details == null)
            {
                this.details = this.LoadDetails();
            }

            return this.details;
        }
    }

    protected virtual TDetails LoadDetails()
    {
        // Some kind of deserialization of TDetails from your DB storage.
    }
}

public class ExampleJob : JobBase<ExampleJob.ExampleJobDetails>
{
    public class ExampleJobDetails
    {
        public string ExampleProperty { get; set; }
        public int AnotherProperty { get; set; }
    }
}

You'd either want to have tables for each type used as TDetails or one big Key/Value based table for all of them. There are pros/cons to both. If you are super paranoid about boxing, there's no reason why TDetails can't be constrained to be a struct, too.

Edit: Got it backwards, you want to save the details on a failure. How about...

public abstract class JobBase<TDetails>
{
    protected TDetails Details { get; private set; }

    public JobBase()
    {
        this.Details = this.CreateDetails();
    }

    protected abstract TDetails CreateDetails();

    protected void SaveDetails()
    {
        // Generic save to database.
    }
}

public class ExampleJob : JobBase<ExampleJob.ExampleJobDetails>
{
    public class ExampleJobDetails
    {
        public string ExampleProperty { get; set; }
        public int AnotherProperty { get; set; }
    }

    protected override ExampleJobDetails CreateDetails()
    {
        return new ExampleJobDetails() { ExampleProperty = "Hi.", AnotherProperty = 1 };
    }
}

Upvotes: 0

Rian Schmits
Rian Schmits

Reputation: 3206

You can make the JobDetails implement an interface and let have BaseJob have an abstract reference to it. Then in the actual jobs you implement the abstract JobDetails with the implementation you want. Then let the JobDetails interface define the methods BaseJob needs to work with. This is a slight variation on the Template Method design pattern. It would look something like this:

public interface IJobDetails {
    void DoSomeWork();
}

public abstract BaseJob {

    protected abstract IJobDetails JobDetails { get; set; }

    public ExecuteJob {
        //execute the job
        JobDetails.DoSomeWork();
    }
}

public Job1 : BaseJob {

    public Job1() {
        JobDetails = new ConcreteJobDetails();
    }

    protected override IJobDetails JobDetails { get; set; }
}

Upvotes: 1

Related Questions