C# Inheritance, new modifier and generics

285 views Asked by At

I'm having a hard trying to find to correct approach to this :

My data structures :

public abstract class Flow
{
    public virtual double Value { get; set; }
    public virtual DateTime Time { get; set; }
}

public class InboundFlow : Flow
{
}

public class OutboundFlow : Flow
{
}

My business objects containing collections of these data structures

public abstract class Fluent
{
    public virtual IList<Flow> FlowCollection { get; set; }
    public virtual double InitialBaseflow { get; set; }
}

public class Affluent : Fluent
{
    public new virtual IList<InboundFlow> FlowCollection { get; set; }
}

public class Effluent : Fluent
{
    public new virtual IList<OutboundFlow> FlowCollection { get; set; }
}

The generic method I'm trying to use :

private static void FindInitialBaseflow<T>(ref T fluent) where T : Fluent
    {
        var linqFluent = fluent;

        var flows = linqFluent.FlowCollection.ToList().FindAll(
                    flow =>
                    flow.Time >= SOME_DATE &&
                    flow.Time < SOME_OTHER_DATE);
        var initialBaseflow = flows.Average(flow => flow.Value);
        fluent.InitialBaseflow = Math.Round(initialBaseflow, 5);  
    }

My problem is that calling "linqfluent.FlowCollection" in the linq method calls for the base class Fluent's FlowCollection, which is null.

How can I force the use of the child's property instead? Thanks!

2

There are 2 answers

0
Servy On BEST ANSWER

You need to make the collection within Fluent generic so that the classes that inherit from it can specify the type:

public class Fluent<T>
    where T : Flow
{
    public IList<T> FlowCollection { get; set; }
    public double InitialBaseflow { get; set; }
}

Once you have that you don't even need sub classes of Flow, you can just make it concrete.

Your use of it would be easily modified to fit this model:

private static void FindInitialBaseflow<T>(Fluent<T> fluent) 
    where T : Flow
{
    var linqFluent = fluent;

    var flows = linqFluent.FlowCollection.Where(
                flow =>
                flow.Time >= SOME_DATE &&
                flow.Time < SOME_OTHER_DATE);
    var initialBaseflow = flows.Average(flow => flow.Value);
    fluent.InitialBaseflow = Math.Round(initialBaseflow, 5);
}

Also note that since you're not setting fluent in this method, there is no need to pass it by reference. It's already a class, so it is itself a reference; mutations of the referenced object will be observed by the caller.

10
P.Brian.Mackey On

Generics are the wrong tool. You should using polymorphism to ensure the correct implementation is called based on the type.

For example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp
{
    public abstract class Flow
    {
        public virtual double Value { get { return new Random().Next() ; } }//these values are just for demonstration purposes
        public virtual DateTime Time
        {
            get
            {
                return DateTime.MinValue.AddYears(1);
            }
        }
    }

    public class InboundFlow : Flow
    {
    }

    public class OutboundFlow : Flow
    {
    }

    public abstract class Fluent
    {
        IList<Flow> _flowCollection;
        public virtual IList<Flow> FlowCollection
        {
            get { return _flowCollection; }
            set { _flowCollection = value; }
        }

        private double _initialBaseflow;
        public virtual double InitialBaseflow
        {
            get { return _initialBaseflow; }
            set { _initialBaseflow = value; }
        }

        public Fluent()
        {
            FlowCollection = new List<Flow>();
        }
    }

    public class Affluent : Fluent
    {
        //public new virtual IList<InboundFlow> FlowCollection { get; set; }//Keep the property polymorphic

        public Affluent()
        {
            FlowCollection = new List<Flow>();
        }
    }

    public class Effluent : Fluent
    {
        //public new virtual IList<OutboundFlow> FlowCollection { get; set; }

        public Effluent()
        {
            FlowCollection = new List<Flow>();
        }
    }

    class Program
    {
        public static DateTime SOME_DATE { get { return DateTime.MinValue; } }
        public static DateTime SOME_OTHER_DATE { get { return DateTime.Now; } }

        static void Main(string[] args)
        {
            var inbound = new InboundFlow();
            var inbound2 = new InboundFlow();
            var outbound = new OutboundFlow();
            var a = new Affluent();            
            a.FlowCollection.Add(inbound);
            a.FlowCollection.Add(inbound2);
            FindInitialBaseflow(a);
        }

        private static void FindInitialBaseflow(Fluent fluent)
        {
            var linqFluent = fluent;

            var flows = linqFluent.FlowCollection.ToList().FindAll(
                        flow =>
                        flow.Time >= SOME_DATE &&
                        flow.Time < SOME_OTHER_DATE);
            var initialBaseflow = flows.Average(flow => flow.Value);
            fluent.InitialBaseflow = Math.Round(initialBaseflow, 5);
        }
    }
}