-3

Context

I have a Question class. It has three subclasses:

  • ChoiceQuestion
  • ShortTextQuestion
  • LongTextQuestion

I have a Repository class which has an IEnumerable<Question>.


Code

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; }
}

Problem

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.


Code

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?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Amal K
  • 4,359
  • 2
  • 22
  • 44
  • 1
    Did you try using the `OfType` method? [https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.oftype?view=net-5.0] – ShayD Apr 11 '21 at 09:46
  • 3
    This question is being discussed on [Meta](https://meta.stackoverflow.com/questions/409008). – cigien Jul 06 '21 at 16:20

2 Answers2

2

One way to do it would be to make your PickOnlySpecificSubclassesOfQuestion generic, and pass it one argument representing the type of question you want.

public void PickOnlySpecificSubclassesOfQuestion<T>()
where T : Question

Inside that method, you could get a System.Type representing the requested type of question like this:

Type desiredTypeOfQuestion = typeof(T);

Then, given an IEnumerable of questions containing questions of all different types, you could use Linq and Reflection to find the ones that are assignable from the desired type:

return myQuestions.Where(q => q.GetType().IsAssignableFrom(desiredTypeOfQuestion));

I'm sure there are other approaches that would work too, but this is the one that sprang to mind most easily.

benjamin
  • 1,364
  • 1
  • 14
  • 26
  • This is a nice solution when I want to work with questions of one subtype. I've ended up using a higher order function instead for a bit more flexibility(described in my answer). Thank you! – Amal K Apr 15 '21 at 06:17
1

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.

Amal K
  • 4,359
  • 2
  • 22
  • 44