I know how to accomplish my goal when using a generic class. But to understand why the syntax of generic methods is allowed (to be declared virtual or abstract) in a non-generic class I can't seem to determine.
The first chunk of code shows a simple example using a generic class to create an abstract method that returns more specific interface than the one constrained in the base class.
public interface ISeries
{
string Name { get; set; }
string Color { get; set; }
}
public interface ITimeSeries<TY> : ISeries
{
double[] XValues { get; set; }
TY[] YValues { get; set; }
}
public interface IPhasorSeries : ISeries
{
double Magnitude { get; }
double Angle { get; }
}
public class PhasorSeries : IPhasorSeries
{
public string Name { get; set; }
public string Color { get; set; }
public double Magnitude { get; set; }
public double Angle { get; set; }
}
public class Series<T> : ITimeSeries<T>
{
public string Name { get; set; }
public string Color { get; set; }
public double[] XValues { get; set; }
public T[] YValues { get; set; }
}
public abstract class Chart<T> where T : ISeries
{
public abstract T GetSeries();
}
public class AnalogChart : Chart<ISeries>
{
public override ISeries GetSeries()
{
return new Series<float>();
}
}
public class PhasorChart : Chart<IPhasorSeries>
{
public override IPhasorSeries GetSeries()
{
return new PhasorSeries();
}
}
Now, if remove the generic type from the Chart class but keep the generic method with the ISeries constraint I get errors on the following.
public abstract class Chart
{
public abstract T GetSeries<T>() where T : ISeries;
}
public class AnalogChart : Chart
{
public override T GetSeries<T>()
{
// ERROR:
// Cannot implicitly convert type Test.Series<float> to 'T'
// Cannot convert expression type Test.Series<float> to return type 'T'
return new Series<float>();
}
}
public class PhasorChart : Chart
{
public override T GetSeries<T>()
{
// ERROR:
// Cannot implicitly convert type Test.PhasorSeries to 'T'
// Cannot convert expression type Test.PhasorSeries to return type 'T'
return new PhasorSeries();
}
}
It appears I don't fully understand what is going on when implementing a generic method in a non-generic class. When a generic class is used--there exist multiple class definitions. What does it mean when you only have a generic method? Are there actually multiple methods? or does it somehow just call the same method?
If this is essentially not supported with abstract and virtual keywords, I would have expected to get an error when creating an abstract generic method. How would one properly use an abstract generic method if you cannot use the syntax here to avoid casting in other code. (I can't even cast the T to an ISeries-- it appears the constraint is lost in the subclass as if it doesn't know T must be an ISeries)
Note the purpose of the question here is to understand what is going on not to work around this problem or have different 'solutions' proposed.
To achieve the desired result I realized that I could use an explicit interface implementation and avoid using generics all together.
public interface IChart
{
ISeries GetSeries();
}
public abtract class Chart : IChart
{
public virtual ISeries GetSeries()
{
throw new NotImplementedException();
}
}
public class AnalogChart : Chart, IChart
{
public new ITimeSeries<float> GetSeries()
{
return new Series<float>();
}
ISeries IChart.GetSeries()
{
return GetSeries();
}
}
public class PhasorChart : Chart, IChart
{
public new IPhasorSeries GetSeries()
{
return new PhasorSeries();
}
ISeries IChart.GetSeries()
{
return GetSeries();
}
}
This allows getting an ISeries if you have a Chart or IChart variable, but allows retrieving a more specific interface if you have a more concrete type. Unfortunately it looks like I can't override the abstract while making it new, so I'm stuck using a virtual method in the abstract class that throws.
I wanted to use the same method name, albeit I could have also just created 'GetPhasorSeries', 'GetTimeSeries' methods instead, but I really wanted to dig in and achieve this specific result.
I was trying to bend generics in a way that they weren't designed. Although I'm still not sure why you would ever create a generic method that is abstract or virtual. I still would welcome any ideas about what pattern uses this construct (given all the context from this question).