0

I have been trying to find an elegant way to avoid repeating code in all of my derived classes. At this point, I am unsure as to the best way to proceed.

I'd like to write a single method in the base class that will instantiate and use any of its derived classes without having to edit the method when I write new derived classes.

I have tried learning/using a generic method but started to think I might be heading down a dead end for this application. I understand that using reflection can be expensive, and since this method is meant to handle hundreds or even thousands of Elements, it seemed like a bad idea.

Now I'm thinking of trying to pass in the class itself as an argument somehow... maybe. That doesn't seem quite right to me either.

I'm willing to do the research, but would love any help pointing me in the right direction.

Here is an abridged version of what I have...

Base Class:

public abstract class Element
{
    public string ElementName { get; }
    public List<string> BadParameters { get; set; } = new List<string>();

    //Constructor
    public Element(string name)
    {
        ElementName = name;
    }

    //The method in question---
    public static List<string> GetBadParameters(//derived class to instantiate)
    {
        var elem = new //derived class();
        return elem.BadParameters;
    }

}

Derived Class 1:

public class Wall : Element
{
    public double Length { get; set; }
    public bool LoadBearing { get; set; }

    //Constructor
    public Wall(string name): base(name)
    {
        SetBadParameters();
    }

    public void SetBadParameters()
    {
        BadParameters = //A wall specific way of setting bad parameters
    }
}

Derived Class 2:

public class Floor : Element
{
    public double Area { get; set; }
    public double Slope { get; set; }

    //Constructor
    public Floor(string name): base(name)
    {
        SetBadParameters();
    }

    public void SetBadParameters()
    {
        BadParameters = //A floor specific way of setting bad parameters
    }
}

Implementation:

public class Implementation
{
    public List<string> GetAllBadElementParameters()
    {
        List<string> output = new List<string>;

        List<string> badWalls = GetBadParameters(//Wall class)
        List<string> badFloors = GetBadParameters(//Floor class)

        output = output.AddRange(badWalls).AddRange(badFloors);
        return output;
    }
}

EDIT - To clarify:

The actual content of

public List<string> BadParameters

does not matter. Bad parameters, how and why they are bad, are inconsequential.

What I'm trying to accomplish is avoid having the method "GetBadParameters()" defined in the derived class, since this method will be the exact same for all derived classes.

It is only the populating of the "BadParameter" base class property that changes from one derived class to another.

EDIT 2 - My attempt at a generic method in the base class:

I know this won't work, but it may convey what I'd like to have happen.

    public static List<string> GetAllBadParameters<T>(List<string> names) where T : ANY DERIVED CLASS, new()
    {
        List<string> output = new List<string>();

        foreach (string name in names)
        {
            var elem = new T(name);
            foreach (string badParameter in elem.BadParameters)
            { 
                output.Add(badParameter); 
            }
        }

        return output;
    }
Leetheus
  • 56
  • 8
  • Content of bad parameters should be same for all derived classes? Or it varies based on instance? – Praveen M Nov 29 '19 at 05:02
  • It varies per instance. In my actual implementation, the contents of bad parameters is set in the constructor of each derived class. – Leetheus Nov 29 '19 at 06:08
  • If it does vary whats the point of putting inside base class? – Praveen M Nov 29 '19 at 06:21
  • The contents of the property "List BadParameters" changes with each instance upon instantiation, but the property itself remains "List". Also, the method "GetBadParameters" does not vary, and in fact has many more lines of code than the simplified version I posted, which is why I do not want to repeat it in the derived classes. – Leetheus Nov 29 '19 at 07:18
  • If the contents are changing there is no point in keeping it in base . In that case what you can do is, make GetBadParameters as virtual and in derived classes you can override this method and call base.GetBadParameters() first (where all your common codes are present) and in overridden methods you can put derived class logic – Praveen M Nov 29 '19 at 08:03
  • 1
    I feel like you didn't explain your problem very well ... I'll try to understand it ;) – Momoro Dec 01 '19 at 01:01
  • @Praveen M I think Momoro may right, and I apologize. But I will need a little more (non-weekend) time to look over your answers and do additional research. I don't want to make any ignorant replies. I will post again once I've had time to try implementing some of your suggestions and/or rethink my design. – Leetheus Dec 01 '19 at 05:25
  • @Leetheus Are you trying to instantiate an object based on a type or string? Do you need something like https://stackoverflow.com/questions/752/how-to-create-a-new-object-instance-from-a-type? You can even use something like Ninject to get an instance based on a string keyword or interface, depending on your needs. – Progman Dec 01 '19 at 14:45
  • @Progman Thank you, the Activator class is exactly what I was looking for. I knew something felt like basic functionality that I was just not aware of. I'm also going to file away your "Ninject" recommendation for additional research and future use. If you change your comment into an answer, I'll mark it as correct. – Leetheus Dec 12 '19 at 18:23

2 Answers2

0

Well … First of all, I am guessing that by "bad parameter" you mean the name of a property in an Element-derived class. For example I'm guessing that if the Length of a Wall is negative then "Length" would be a bad parameter of that particular Wall. Secondly I'm guessing that you are going to have a largish number of elements, e.g. a number of walls and floors (and other things) in a diagram or whatever.

Assuming that, then one way to do this would be to have an abstract method in the Element class that returns the bad parameters, and implement it in each derived class. Something like this:

public abstract class Element  
{
    public string Name { get; private set; }
    public abstract IList<string> GetBadParameters();
    public Element( string name) { this.Name = name; }
}

public class Wall 
{
    public Wall( string name): base(name) {}
    public double Length { get; set; }
    public bool IsLoadBearing { get; set; }

    public IList<string> GetBadParameters() {
         List<string> bad = new List<string>();
         if (this.Length <= 0) { bad.Add( this.Name + ": " + nameof( this.Length); }
         if (this.IsLoadBearing && this.Length > whatever) { bad.Add( this.Name + ": " + nameof( this.IsLoadBearing); }
         return bad;
    }
}

Then if you had a list of all the elements you could get all the bad parameters by

IList<string> allBadParemeters = elements.SelectMany( e => e.GetBadParameters() ); 

What I would say though is that this might not be such a great design. You would end up with a system in which a lot of elements contain bad parameters. Life could be a lot easier if you just prevent bad parameters from happening in the first place. You can do this by making the 'set' methods of all the parameter properties private and adding a method such as bool Wall.TrySetParameters( double length, bool isLoadBearing). If the parameters are bad then this would just return false and not assign the parameters to the wall. If you want to have TrySetParameters in the base class then you could do it with a more general signature such as

 public struct Parameter { 
     public Parameter( string name, object value) { … } 
     public string Name { get; private set; }
     public object Value { get; private set; }
 }

 abstract public class Element { 
    …
    abstract public bool TrySetParameters( params Parameter[] parameters); 
 }
sjb-sjb
  • 1,112
  • 6
  • 14
  • I'm sorry, I tried to simplify my code and ended up removing context. I will edit my question to clarify. Essentially, your solution still has me repeating code (public IList GetBadParameters() {}) in all of my derived classes. Thank you anyway. – Leetheus Nov 29 '19 at 03:41
  • I stand by my design. There is no repeated code because, as you say, the method of populating your BadParameters list differs from class to class. In my design I've skipped the storing of the BadParameters in an array and gone directly to a method that returns them. Whether they should be stored or not really depends on how long it takes to calculate them and how often you are accessing them. My bet is that in most applications they are fairly easy to calculate and therefore storing them in an array would just waste space. Also, storing them creates the possibility of them becoming outdated. – sjb-sjb Nov 30 '19 at 13:58
  • P.S. Instead of constructing a list in the BadParameters() method in my suggested approach, one could change the return type to IEnumerable and then use "yield return" in the body of the method. This would avoid creating the list in some situations. – sjb-sjb Nov 30 '19 at 14:00
  • Again, a possibly better design would be to just prevent assigning bad parameters in the first place. Then you know that the objects are always valid. Another approach would be to use the validation attributes in System.ComponentModel.DataAnnotations (such as RequiredAttribute or derive your own) and/or implement the IValidatableObject interface. Then call Validator.TryValidateObject to validate the object. – sjb-sjb Nov 30 '19 at 14:06
  • Thank you, I will take some time to consider your suggestions (when I'm not on my day off). I will quickly say, though, that preventing bad parameters is not possible in my application. I'm working with the Revit 3D modeling software API. Within that context, there are parameters I need elements to have that are not built in. For example, I need elevated floors to have the parameter "DistanceToBelow". If I have a floor on levels 2 and 3, but then change the elevation of the level 2 floor, the "DistanceToBelow" for any level 3 floors would now be a "BadParameter" that needs updating. – Leetheus Dec 01 '19 at 05:27
0

I am assuming your BadParameter list content is same for all derived Classes. If this list is not common then there is no point in filling these list in Base Class. By that assumption I can suggest you following changes .

Your base class looks like this . There is no need of making GetBadParameters() as static

public abstract class Element
    {
        public string ElementName { get; }
        public List<string> BadParameters { get; set; } = new List<string>();

        //Constructor
        public Element(string name)
        {
            ElementName = name;
        }

        /// <summary>
        /// Tjis Method is common for alal derived classes. Assuming content is same for all dervied class
        /// </summary>
        /// <returns></returns>
        //The method in question---
        public List<string> GetBadParameters()
        {
            return new List<string>() { "1", "2" };
        }

    }

Your first derived class Wall, where it will call GetBadParameters from base .

 public class Wall : Element
    {
        public double Length { get; set; }
        public bool LoadBearing { get; set; }

        //Constructor
        public Wall(string name) : base(name)
        {
            SetBadParameters();
        }

        public void SetBadParameters()
        {
            BadParameters = GetBadParameters();//Calling base GetBadParameters
        }
    }

Same goes with second derived class "Floor"

 public class Floor : Element
    {
        public double Area { get; set; }
        public double Slope { get; set; }

        //Constructor
        public Floor(string name) : base(name)
        {
            SetBadParameters();
        }

        public void SetBadParameters()
        {
            BadParameters = GetBadParameters();//Calling base GetBadParameters
        }
    }

In your implementation class, you can create both wall and floor objects by keeping Element class as reference and call respective GetBadParameters

public class Implementation
    {
        public List<string> GetAllBadElements()
        {
            List<string> output = new List<string>;

            Element _wall = new Wall("wall");
            Element _floor = new Floor("floor");
            List<string> badWalls = _wall.GetBadParameters(); //Returns Wall bad Parameters
            List<string> badFloors = _floor.GetBadParameters(); //Returns Floor bad Parameters

            output = output.AddRange(badWalls).AddRange(badFloors);
            return output;
        }
    }
Praveen M
  • 443
  • 2
  • 11