I have a Question
class. It has three subclasses:
ChoiceQuestion
ShortTextQuestion
LongTextQuestion
I have a Repository
class which has an IEnumerable<Question>
.
Question class hierarchy and container class Repository:
class Question {}
class ChoiceQuestion : Question {}
class ShortTextQuestion : Question {}
class LongTextQuestion : Question {}
class Repository
{
IEnumerable<Question> Questions { get; set; }
}
I want to pick few questions for a Questionnaire
from these repositories.
I have an IQuestionnaireBuilder
which has an AddSource()
method that helps configure which repository to pick questions from and how to pick them. I have the QuestionnaireSource
class which holds this configuration.
Currently, I am able to specify, which repository to pick from, how many questions to pick of each difficulty. I want to specify that it should only pick questions which are of specific subtypes.
For instance, the questions to be picked must be either ChoiceQuestion
or ShortTextQuestion
. I have come across System.Type
, but I want to restrict the types such that they must derive from Question
.
IQuestionnaireBuilder
interface IQuestionnaireBuilder
{
IQuestionnaireBuilder AddSource(Action<QuestionnaireSource> source);
}
QuestionnaireSource
class QuestionnaireSource
{
public Repository Repository { get; set; }
public IDictionary<QuestionDifficulty, int> { get; set; }
// <Property/method to configure which subclasses to include, they must derive from Question>
}
QuestionDifficulty
enum QuestionDifficulty
{ Easy, Medium, Hard }
IQuestionnaireBuilder builder = new QuestionnaireBuilder();
Repository repo1 = someExternalProvider.GetRepo(1);
Repository repo2 = someExternalProvider.GetRepo(2);
builder
.AddSource(source => {
source.Repository = repo1;
source.Count[QuestionDifficulty.Easy] = 10;
source.Count[QuestionDifficulty.Medium] = 7;
source.Count[QuestionDifficulty.Hard] = 3;
source.PickOnlySpecificSubclassesOfQuestion() // how to implement this?
})
.AddSource(source => {
source.Repository = repo2;
source.Count[QuestionDifficulty.Easy] = 30;
source.Count[QuestionDifficulty.Medium] = 15;
source.Count[QuestionDifficulty.Hard] = 5;
source.PickOnlySpecificSubclassesOfQuestion() // how to implement this?
})
In the above snippet, how do I implement the PickOnlySpecificSubclassesOfQuestion()
part?
This is how I've done it using delegates. I added a Filter
property to QuestionnaireSource
of type System.Predicate<T>
. System.Predicate<T>
is a delegate defined as follows:
public delegate bool Predicate<in T>(T obj);
You can also use Func<Question, bool>
or your own delegate type.
In my QuestionnaireSource
class, I added the Filter
property:
class QuestionnaireSource
{
public Repository Repository { get; set; }
public IDictionary<QuestionDifficulty, int> { get; set; }
public Predicate<Question> Filter { get; set; }
}
Now I can pass a lambda expression such as the following:
question => question is ChoiceQuestion
Now I can filter more flexibly thanks to C#'s pattern matching with is
.
I can configure it as following using the AddSource()
method of QuestionnaireBuilder
when building my questionnaire:
builder
.AddSource(source => {
source.Repository = repo1;
source.Count[QuestionDifficulty.Easy] = 10;
source.Count[QuestionDifficulty.Medium] = 7;
source.Count[QuestionDifficulty.Hard] = 3;
source.Filter = question => question is ChoiceQuestion
});
Now I can filter out or include multiple types:
question => question is ChoiceQuestion || question is ShortTextQuestion
Not only that, I can also filter using other criteria, such as the question text:
question => question.Text.Trim().StartsWith("What");
@benjamin's answer also works if you want to select just one subtype but this filter approach seems to be a bit more flexible if multiple types are to be selected or ignored.