Why can't you make static methods virtual or abstract? #63448
-
I was working on a project where I needed to get a certain result, but there's multiple approaches to calculate that result. Let me use a triangle as an analogy. A triangle is most commonly represented by its base and height, but you could also represent it as three lengths, one for each side. In both cases, I could ask for the area and for the perimeter of the triangle. Using the first representation, the area is very easy to calculate, but the perimeter is not as straightforward. Using the second representation, the perimeter calculation gets much easier, but the area calculation is also pretty simple using Heron's formula. When you think about it, the most common 2D shapes that we can think of (triangles, circles, rectangles, polygons, ...) have an area and a perimeter. So if I wanted to represent those shapes in code, it would make sense that I had a Not necessarily. For example, how would you write a triangle class that uses either base/height or the side lengths for the calculations? Make two classes? Add all the properties into a single class and encapsulate them with a constructor? The latter might work, but for every new type of area or perimeter calculation with different parameters, you would have to add properties to the triangle class and write the appropriate methods. Let's forget about representing a triangle as a class for now, and focus on what we actually want: an area calculation and a perimeter calculation that's specific to each shape. That detail, Specific to each shape, seems like a good reason to create a static method in each derived class, preferably using a signature compatible with its parent method. // Note: this code doesn't compile
public abstract Shape
{
public abstract static double GetArea(IAreaArguments areaArgs); // Or virtual in a non-abstract class
public abstract static double GetPerimeter(IPerimeterArguments perimArgs); // Or virtual in a non-abstract class
}
public Triangle : Shape
{
public override static double GetArea(ITriangleAreaArguments areaArgs)
// ITriangleAreaArguments : IAreaArguments
{
double area = areaArgs switch
{
BaseHeightTriangleAreaArguments bhArgs => GetBaseHeightArea(bhArgs),
HeronAreaTriangleArguments heronArgs => GetHeronArea(heronArgs),
_ => throw new ArgumentException("Cannot calculate area based on the given arguments.", nameof(areaArgs));
};
return area;
}
public override static double GetPerimeter(ITrianglePerimeterArguments perimArgs)
// ITrianglePerimeterArguments : IPerimeterArguments
{
double perimeter = perimArgs switch
{
HeronTrianglePerimeterArguments heronArgs => GetHeronPerimeter(heronArgs),
_ => throw new ArgumentException("Cannot calculate perimeter based on the given arguments.", nameof(perimArgs));
};
return perimeter;
}
// Private implementations of the individual calculations using different arguments
private static double GetBaseHeightArea(BaseHeightTriangleAreaArguments bhArgs)
=> bhArgs.Base * bhArgs.Height / 2.0;
private static double GetHeronArea(HeronTriangleAreaArguments heronArgs)
{
var (a, b, c) = (heronArgs.A, heronArgs.B, heronArgs.C);
double s = (a + b + c) / 2.0;
return Math.Sqrt(s * (s - a) * (s - b) * (s - c));
}
private static double GetHeronPerimeter(HeronTrianglePerimeterArguments heronArgs)
=> heronArgs.A + heronArgs.B + heronArgs.C;
} Obviously, you'd still have to write the method implementations for the various arguments, but none of the derived classes would have to contain any of the data required to do the calculations. You'd only have to write the abstract methods for the base class and you wouldn't be able to forget implementing them in derived classes like you would if you had to remember to add the right static methods to each derived class manually. This implementation can be done without static methods and using generics, but it's not quite the same: // Note: this code doesn't compile
public abstract class Shape2D<TAreaArguments, TPerimeterArguments> : IShape2D<TAreaArguments, TPerimeterArguments>
where TAreaArguments : IAreaArguments
where TPerimeterArguments : IPerimeterArguments
{
public abstract double GetArea(TAreaArguments areaArguments);
public abstract double GetPerimeter(TPerimeterArguments perimeterArguments);
}
public class Triangle : Shape<ITriangleAreaArguments, ITrianglePerimeterArguments>
{
public double GetArea(ITriangleAreaArguments areaArguments)
=> areaArguments switch
{
BaseHeightTriangleAreaArguments bhArgs => GetBaseHeightArea(bhArgs),
HeronTriangleAreaArguments heronArgs => GetHeronArea(heronArgs),
_ => throw new ArgumentException("Cannot calculate area from the given arguments.", nameof(areaArguments))
};
public double GetPerimeter(ITrianglePerimeterArguments perimeterArguments)
=> perimeterArguments switch
{
HeronTrianglePerimeterArguments heronArgs => GetHeronPerimeter(heronArgs),
_ => throw new ArgumentException("Cannot calculate perimeter from the given arguments.", nameof(perimeterArguments))
};
// Private implementations are static and are the same as above
} You would still need an instance of So, why can't you make static methods virtual or abstract? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
C# 10 adds support for static abstract interface methods as a preview feature. |
Beta Was this translation helpful? Give feedback.
C# 10 adds support for static abstract interface methods as a preview feature.
https://devblogs.microsoft.com/dotnet/welcome-to-csharp-10/#preview-features
https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/