Static classes should not be sealed. #1631
Replies: 34 comments
-
|
Beta Was this translation helpful? Give feedback.
-
In my opinion there is a good reason, why static classes are sealed. You can't instanciate an object of a static class, so a static class isn`t really an object, which means you cant inherit from it. Basically you want something like a partial class, to extend it. |
Beta Was this translation helpful? Give feedback.
-
It doesn't make much sense for a type that cannot be instantiated to participate in a type hierarchy, not even to promote the "abusive" use of attempting to invoke static members through a derived type. Feel free to not use the |
Beta Was this translation helpful? Give feedback.
-
@Joe4evr: Why does @HaloFour: How is this abusive? How does it not make sense for a static class to participate in a type hierarchy? Every discussion on this design choice I have found just has people repeating what literally is while completely failing to justify why it is as it is. I see no reason why I suppose a similar language feature would be Ruby's modules. That's a bit of a stretch but the best comparison to be made. Sometimes composition doesn't make any sense and you need to slide things in to where they already are. I suppose I could use normal classes for this as @HaloFour has indicated An alternative would be to allow namespaces to contain members, but I suspected that would be even more unpopular. |
Beta Was this translation helpful? Give feedback.
-
Calling a static method through a derived type name is itself the abuse. Static methods do not participate in virtual dispatch and it's not possible for the derived type to override the implementation. The compiler is doing the work of looking up the method on base classes and then stamping that into the static method call, where that method doesn't properly exist according to the CLR. It's a bug vector.
|
Beta Was this translation helpful? Give feedback.
-
Because
If you want to do that, use managed C++. C# though provides
Because they make clear to the developer that the contents are all static too. It's "pit of success" stuff in that it helps prevent a future developer accidentally adding an instance member to that class.
Please see #1180 for a discussion on that topic. |
Beta Was this translation helpful? Give feedback.
-
Just because the current implementation in the compiler is bugridden doesn't mean the ultimate goals of the feature are not useful. So, to clarify, in this example: namespace Project
{
class A
{
public static void m()
{
}
}
class B : A
{
public static void m()
{
}
public static void n()
{
}
}
} Calling |
Beta Was this translation helpful? Give feedback.
-
No, calling
This approach is not very helpful. You're just going to very quickly turn people off from wanting to engage you. The compiler isn't bugridden. Also, this has nothing to do with inheritance. Static members aren't inherited. They belong specifically to that type. It's not possible to mark a static member as virtual and it's not possible to override a static member. |
Beta Was this translation helpful? Give feedback.
-
I apologize for my tone. I do not want to turn people off. But at some point I do need to say that you are not making any sense nor are you furthering your argument. Most people are not going to like hearing that regardless of how it is phrased. @DavidArno: You seem to be copying whatever the original developers wrote without thinking about it. You have completely failed to justify the current behavior of the compiler. Stop telling me what is and why what is should be. I still have no idea why static classes should be implemented the way they are, and no one here seems to have any information on the matter. The bug report you linked is going much the same way as this conversation, though I thank you for linking it. It is good to know that some people like the feature. The comments have a useful quote:
@HaloFour: If If I want a c;ass to be
What does this mean? That I'm pretty sure this has everything to do with inheritance, as it is how your argument against the feature is presented ("abuse of inheritance") and what I wish static classes to be able to implement. |
Beta Was this translation helpful? Give feedback.
-
To be fair to @R030t1, I agree that, with hindsight, this was a poor decision on the part of the C# designers. If But there's no point in crying over spilt milk and all that. The language is what it is. It gives the impression of inheritance, but it isn't and never will be. It's often a confusing lesson to learn, but we all learn it and move on... |
Beta Was this translation helpful? Give feedback.
-
The issue is already "niche" but also very easy to workaround today. If you find a use case where exposing only static members, but in some form of logical hierarchy, it is not difficult to just drop the I will also say that, in some cases, it makes sense to create a class that only exposes static methods and that does so as part of a logical hierarchy. The Hardware Intrinsics APIs are looking at doing this approach in CoreCLR/CoreFX. We only expose static methods and the various classes ( I can also think of at least one other place where doing this might have made code easier to read/use ( |
Beta Was this translation helpful? Give feedback.
-
You can inherit without the ability to override, such as is the case with non-virtual instance methods. You also still have the ability to hide the method (where the compiler will warn you to add the |
Beta Was this translation helpful? Give feedback.
-
@R030t1, it was likely done the way it was because it was a convenient way to encode the data and provide the correct enforcements. Yes it could have been done another way (such as with an attribute and actual validation), but that doesn't provide any actual enforcement (you can still "break" that by using another language or manually writing IL, etc). At this point, it probably won't be changed as it would be considered a breaking change (it will silently break the "contracts" people have created for their APIs). |
Beta Was this translation helpful? Give feedback.
-
@tannergooding: My confusion is that To me it seems there is still something missing from your explanation. Why was
I don't understand this concern. The reason is likely related to my above comment: why is
Existing compiled code can remain unchanged and would still interoperate with new code. Existing source could have It is still a non-additive syntactic change, so perhaps it could be held to a major version, similar to some of Python 3's changes? |
Beta Was this translation helpful? Give feedback.
-
It is very much a goal of the C# team to avoid fracturing the ecosystem. There's no reason to remove the |
Beta Was this translation helpful? Give feedback.
-
@R030t1 Going off of what @jnm2 said, if this change was somehow made, now people can would be able to instantiate classes that were previously not able to be instances. Consider something like this: public static class ParseManager
{
// do static-only stuff
} This class will never need to be an instance, nor will it need to be inherited from. However, if you changed that, someone could inherit from it and then create as many instances as they want. This is bad. We only need the one It would be a better alternative to have a new keyword (probably contextual) public unsealed static class ParseManager
{
// do static-only stuff
} This is better because it won't change the behavior of old code. This type of class will not be able to be instantiated unless inherited from (and is able to be inherited from), much like an Difference between Also, the CLR calls |
Beta Was this translation helpful? Give feedback.
-
FWIW, it sounds like this may be resolved by the ability to extend static types (specifically the addition of static methods or properties via Extension Everything). I've been pushing for this ability as part of expanding the |
Beta Was this translation helpful? Give feedback.
-
@R030t1 For example, if we define a static class A public static class A
{
public static void M() {}
} and we then allow other types to derive from A: public class B : A
{
} then that means that we can, essentially, create an instance of the 'static' class A: var x = (A)new B(); This has broken the meaning of 'static'. I shouldn't be able to use the name Not to mention the fact that for a class to participate in a hierarchy it must have a constructor. But, a static class (by definition) cannot have a constructor. Sure, C# might be able to get around these facts using private inheritance, but I've got no idea how you would represent that in IL, let alone whether or not that would be a good idea. |
Beta Was this translation helpful? Give feedback.
-
Not sure why C# prevents me using System.Diagnostics;
namespace Microsoft.Extensions.Logging
{
public class TLog<T> where T : ILogCategory
{
// protected works, but I do not need protected. I want private and private makes sense.
private TLog()
{
throw new System.NotSupportedException("https://github.com/dotnet/csharplang/issues/1631");
}
static TLog() => Log = TLog.Factory.CreateLogger(typeof(T).Name);
/// <summary> Gets the log.</summary>
public static ILogger Log { get; }
}
/// <inheritdoc/>
public sealed class Debug : TLog<Debug>, ILogCategory
{
/// <summary>
/// Traces the specified message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="args">The arguments.</param>
[Conditional("UNITY_EDITOR")]
public static void Trace(string message, params object[] args) => Log.LogTrace(message, args);
}
/// <inheritdoc/>
public sealed class Network : TLog<Network>, ILogCategory
{
}
/// <summary>
/// Well known categories.
/// </summary>
public static class TLog
{
private static LoggerFilterOptions loggerOptions;
private static ILoggerConfiguration loggerConfig;
/// <summary>
/// Gets default logging factory.
/// </summary>
public static ILoggerFactory Factory { get; private set; }
// ... factory setup methods
}
} |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@dzmitry-lahoda In order to obtain an instance of Constructor accessibilities that allow inheritance by any other class within the same assembly:
|
Beta Was this translation helpful? Give feedback.
-
I do not want to call instance ctor never ever and I never want to instance // I guess both calls do same thing
MLog<Network>.Log.LogWarning("Disconnected from server. ");
Network.Log.LogWarning("Disconnected from server. ");
// extension method
Network.Log.Fail(serverInfo);
Network.Log.Fail(serverInfo); // ConnectionInfo is static method possibly behind Conditional to avoid code in Production
MLog<Network>.ConnectionInfo(new MyConnectionInfo(..));
Network.Log.ConnectionInfo(new MyConnectionInfo(..)); Short hands:
// used to collect all categories to create GUI for filters which than stores stuff in file which calls concrete MLog to setup ILoggerFactory upon Unity game start up
allCategories = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(x => x.GetTypes())
.Where(x => x.GetInterfaces().Contains(typeof(ILogCategory))).ToList(); |
Beta Was this translation helpful? Give feedback.
-
All classes need constructors and all constructors are required to call their base constructor. Whether or not you intend for these types to actually be creatable you must still follow those rules. You can make the constructor |
Beta Was this translation helpful? Give feedback.
-
For the case of issue /// <inheritdoc/>
public abstract class Any<T> : TLog<T>, ILogCategory where T :ILogCategory
{
/// <summary>
/// Traces the specified message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="args">The arguments.</param>
[Conditional("UNITY_EDITOR")]
public static void Trace(string message, params object[] args) => Log.LogTrace(message, args);
}
/// <inheritdoc/>
public sealed class Debug : Any<Debug>, ILogCategory
{
[Conditional("DEBUG")]
public static void MyDebug(string message, params object[] args) => Log.LogTrace(message, args);
} So for my case it is And I still want some classes to be in my I guess CLR allows to do what I want, but for some reason C# prevents me from doing so. I would rather C# to show me error if I will try in ctor(instantiate) that classes, not when I declare them. |
Beta Was this translation helpful? Give feedback.
-
The CLR requires that every type has a constructor and that every constructor call the base constructor, otherwise the assembly is unverifiable. |
Beta Was this translation helpful? Give feedback.
-
CLR does not require that. I can
|
Beta Was this translation helpful? Give feedback.
-
Try running that assembly through |
Beta Was this translation helpful? Give feedback.
-
@dzmitry-lahoda - Forgive me, I'm slightly lost. From what I understand of the code samples, you want a public base class that cannot be directly instantiated. Why is an abstract class not suitable for your purpose? |
Beta Was this translation helpful? Give feedback.
-
Mentioned Sse2 to Sse relation used
Examples:
For any console app for .net core 2.2 (file does exists, there is other special error for wrong path)
And another angle to view from Scala on static classes |
Beta Was this translation helpful? Give feedback.
-
You can't have classes without constructors and you can't have inheritance without calling base constructors. This is a rule of the CLR.
IL can do a lot of things that C# can't do. In this particular case you are doing something considered inappropriate by IL.
I don't understand why using What's so horribly wrong about being able to create an instance of one of these types anyway? It's not like it breaks your code in any way. At worst the developer is sitting there with an instance that they can't do anything with. That's probably the least egregious sin that can happen when prototyping a project.
Scala doesn't have static classes, it has companion objects. They are singleton instances of a class that happens to share a name with a normal class. Their constructors are called. And while they can inherit from another class, they can't inherit from another companion object. The JVM also has the same restriction as the CLR. Any derived class constructor must call the base class constructor. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
For a usecase, see this PInvoke issue. The current behavior of C# leads to the inability to do things like the following with static classes:
Beta Was this translation helpful? Give feedback.
All reactions