C # (.NET) Design Flaws

What are some of the biggest design flaws in C # or the .NET Framework as a whole?

Example: There is no null string type, and you should check DBNull when retrieving values ​​from IDataReader.

+85
design c #
Jan 04 '09 at 23:29
source share
33 answers
  • one
  • 2

I strongly agree with this post (for those who poo-pooing the absence of ToString, there is a debugger attribute to provide a custom format for your class).

In addition to the list above, I would also add the following reasonable queries:

  • non-empty reference types as a complement to NULL value types,
  • allows you to override the constructor empty constructor
  • allow general type restrictions to specify private classes,
  • I agree with another poster here that requested arbitrary constructor entries when used as constraints, i.e. where T: new (string), or where T: new (string, int),
  • I also agree with another poster here about fixing events, both for empty event lists and in parallel settings (although the latter is complicated), Operators
  • should be defined as extension methods, and not as static methods of a class (or not only as static methods, at least),
  • allow static properties and methods for interfaces (Java has it, but C # doesn't)
  • allow event initialization in object initializers (currently only fields and properties are allowed),
  • Why is the syntax "object initializer" used only when creating the object? Why not make it available at any time, i.e. var e = new Foo (); e {Bar = baz};
  • fix quadratic enumerated behavior,
  • all collections should have unchanged snapshots for iteration (i.e. mutation of the collection should not result in cancellation of the iterator)
  • it’s easy to add, but an effective closed algebraic type of type “Either” is not so, so I would like to somehow declare a closed algebraic type and apply exhaustive pattern matching to it (basically first-class support for the visitor’s pattern, but much more efficient); so just grab the enumerations, extend them with comprehensive template matching support, and avoid any invalid cases,
  • I would really like the support for templates in general, but at least for testing the type of object; I also like the switch syntax suggested in another post here,
  • I agree with another post that System.IO classes such as Stream are somewhat poorly designed; any interface that requires some implementations for throw NotSupportException is a bad design,
  • IList should be much simpler than it is; in fact, this may be true for many specific collection interfaces, such as ICollection,
  • too many methods throw exceptions, like IDictionary,
  • I would prefer the form of checked exceptions to be better than the one available in Java (see the study of type systems and effects, how to do this),
  • fix various annoying corner cases when resolving the overload of the general method; for example, try providing two overloaded extension methods, one of which works with reference types, and the other with types of NULL structures, and see how you like your output type,
  • provide safe display of field names and names for interfaces such as INotifyPropertyChanged, which accept a field name as a string; you can do this using an extension method that accepts a lambda using a MemberExpression expression, i.e. () => Foo, but this is not very efficient,
  • allow operators in interfaces and do all types of basic IArithmetic types; other useful common operator interfaces are possible,
  • complicate the mutation of the fields / properties of the object, or at least allow the annotation of immutable fields and force the type checker to apply it (just treat it as a getter-only fer chrissakes property, it's not difficult!); in fact, to unify fields and properties in a more reasonable way, since it makes no sense to have both; The automatic features of C # 3.0 are the first step in this direction, but they are not advanced enough,
  • simplify the declaration of constructors; I like the F # approach, but another post here that just needs a “new” instead of a class name is at least better

So far, I suppose. These are all the annoyances that I encountered last week. I probably could go on for hours if I really thought about it. C # 4.0 already adds named, optional, and default arguments, which I strongly approve of.

Now for one unreasonable request:

  • it would be nice indeed, indeed , if C # / CLR can support type constructor polymorphism, i.e. generics over generics

Pretty please? :-)

+39
Jul 25 '09 at 6:21
source share
  • The Reset() method on IEnumerator<T> was an error (for iterator blocks, the language specification even requires that it throw an exception)
  • reflection methods that return arrays were eric in Eric's view
  • The covariance of arrays was and remains strange; at least in C # 4.0 / .NET 4.0 this is done correctly for IEnumerable[<T>]
  • ApplicationException rather fell out of favor - was it a mistake?
  • synchronized collections are a good idea, but not necessarily really useful: you usually need to synchronize several operations ( Contains , then Add ), so a collection that synchronizes individual operations is not so useful
  • it was possible to use more of the using / lock pattern - perhaps allowing them to use reusable (extensible?) syntax; you can mimic this by returning IDisposable and using using , but it could be clearer
  • iterator blocks: there is no easy way to check arguments ahead of time (rather than lazily). Of course, you can write two chained methods, but this is ugly.
  • simple immutability would be enjoyable; C # 4.0 helps a little but not enough
  • no "this ref-type parameter cannot be null", although contracts (in 4.0) help with this. But syntax like Foo(SqlConnection! connection) (which introduces a null-check / throw ) will be nice (contrast with int? Etc.)
  • lack of support for operators and non-standard designers with generics; C # 4.0 solves this a bit using dynamic , or you can enable it like this



  • an iterator variable is declared outside the while while in the foreach extension, which means that anon-methods / lambdas fix one variable, not one per iteration (it hurts with threading / async / etc)
+72
Jan 04 '09 at 23:39
source share

TextWriter is the base class of StreamWriter. WTF?

It always bothers me to the extreme.

+60
Jan 05 '09 at 13:32
source share

The small C # pet peev constructors use the C ++ / Java syntax so that the constructor is the same name as the class.

New() or ctor() would be much nicer.

And of course, tools like coderush make this less of a problem for renaming classes, but from the readability of POV, New () provides more clarity.

+44
Jan 05 '09 at 0:40
source share

I do not understand what you cannot do

where T: new (U)

So you declare that the generic type T has a constructor that is different from the standard.

change

I want to do this:

 public class A { public A(string text) { } } public class Gen<T> where T : new(string text) { } 
+29
Jan 05 '09 at 0:14
source share
  • I'm not a big fan of Stream, StringWriter, StringReader, TextReader, TextWriter classes ... it's just not intuitive what it is.
  • IEnumerable.Reset throws an exception for iterators. I have third-party components that always cause reset when binding to the database, first I need to use a list to use.
  • The Xml Serializer must have serialized IDictionary elements.
  • I completely forgot about the HttpWebRequest and FTP API, which is a pain in my .... (thanks for the comment by Nicholas to remind me of this :-)

Edit
5. Another annoyance of mine is how System.Reflection.BindingFlags has different uses depending on the method you use. In FindFields, for example, what does CreateInstance or SetField mean? This is the case when they overload the meaning of this enumeration, which is confusing.

+20
Jan 05 '09 at 0:28
source share

I am very surprised that I am the first to mention this:

ADO.NET datasets do not display nullable columns as properties of nullable types. You should be able to write this:

 int? i = myRec.Field; myRec.Field = null; 

Instead, you should write this, which is just plain stupid:

 int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field; myRec.SetFieldNull(); 

This was annoying in .NET 2.0, and now it has become even more annoying that you need to use jiggery-pokery as mentioned above in your nice neat LINQ queries.

It is also annoying that the generated Add<TableName>Row method is also insensitive to the concept of types with a null value. Moreover, the generated TableAdapter methods TableAdapter not.

There is not much in .NET that makes me feel like the development team said, "Okay, boys, we're close enough, we're sending!" But that's for sure.

+20
Jan 05 '09 at 7:55
source share

I do not know what I would say that this is a design error, but it would be very nice if you could output the lambda expression in the same way as you can in VB:

VB:

 Dim a = Function(x) x * (x - 1) 

FROM#

It would be nice if this could be done:

 var a = x => x * (x - 1); 

Instead of this:

 Func<int, int> a = x => x * (x - 1); 

I understand this is not much longer, but in Code Golf every character counts damn! Don't they take this into account when developing these programming languages? :)

+15
Jan 05 '09 at 0:00
source share
  • System.Object class:

    • Equals and GetHashCode - not all classes are comparable or hashed; they should be transferred to the interface. IEwatable or IComparable (or the like) comes to mind.

    • ToString - not all classes can be converted to a string, must be moved to the interface. IFormattable (or the like) comes to mind.

  • ICollection.SyncRoot property:

    • Promotes poor design, an external lock is almost always more useful.
  • In the beginning, there should be common functions:

    • The System.Collections namespace contains many more or less obsolete classes and interfaces.
+13
Jan 05 '09 at 13:29
source share

One of the things that annoys me is the Predicate<T> != Func<T, bool> paradox. They are both delegates of type T -> bool , and yet they are not compatible with the destination.

+12
Jan 05 '09 at 23:00
source share

Some people (ISVs) want you to be able to compile it for machine code at build time and link it to create your own executable that does not require dotNet runtime.

+11
Jan 04 '09 at 23:34
source share

I don't like the C # switch statement.

I would like something like this

 switch (a) { 1 : do_something; 2 : do_something_else; 3,4 : do_something_different; else : do_something_weird; } 

Thus, there are no more gaps (easy to forget) and the ability to comma - to separate different values.

+11
Jan 24 '09 at 12:52
source share

We know so much about right OO methods. Decoupling, contract programming, exclusion of inappropriate inheritance, proper use of exceptions, open / closed principle, Liskov substitution, etc. However, the .NET framework does not use best practices.

For me, the biggest flaw in .Net design is not on the shoulders of giants; promotes less ideal programming paradigms to the masses of programmers who use their framework .

If MS paid attention to this, the software world could make big leaps in terms of quality, stability and scalability this decade, but, alas, it seems to regress.

+10
Jan 05 '09 at 2:59
source share

Events in C # where you must explicitly check listeners. Wasn’t it connected with events to broadcast, whoever it was? Even if they are not?

+10
Feb 03 '09 at 19:46
source share

Horrible (and completely invisible to most people) O (N ^ 2) nested / recursive iterators behavior.

I am very gutted that they know about it, I know how to fix it , but it is not considered as having sufficient priority to deserve inclusion.

I work all the time with tree-like structures and must correct otherwise smart people code when they inadvertently enter expensive operations in this way.

The beauty of yield foreach is that a simpler and simpler syntax contributes to the correct, efficient code. This is the “success” that I think they should strive for before adding new features for the long-term success of the platform.

+9
Feb 21 '09 at 16:18
source share

Some classes implement interfaces, but do not implement many of the methods of this interface, for example, Array implements IList, but 4 out of 9 methods throw a NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members.aspx

+7
Jan 05 '09 at 1:08
source share

Static elements and nested types in interfaces.

This is especially useful when an interface member has a parameter of a type specific to the interface (e.g. enum ). It would be nice to nest the enum type in the interface type.

+7
Jan 05 '09 at 1:22
source share

The default hard nature of events. The fact that you can trigger an event and be in a conflicting state due to the removal of subscribers is simply terrible. Mass media

+6
Jun 30 '09 at 2:38
source share
  • null

  • const nowhere.

  • APIs are incompatible, for example. mutating the array returns void , but adding to StringBuffer returns the same mutable StringBuffer .

  • Collection interfaces are not compatible with immutable data structures, for example. Add to System.Collections.Generic.IList<_> cannot return the result.

  • There is no structural typing, so you write System.Windows.Media.Effects.SamplingMode.Bilinear instead of just Bilinear .

  • Mutable IEnumerator interface implemented by classes when it should be an immutable struct .

  • Equality and comparison is mess: you have System.IComparable and Equals , but you also got System.IComparable<_> , System.IEquatable , System.Collections.IComparer , System.Collections.IStructuralComparable , System.Collections.IStructuralEquatable , System.Collections.Generic.IComparer and System.Collections.Generic.IEqualityComparer .

  • Tuples must be structures, but structures unnecessarily block the elimination of tail calls, so one of the most common and fundamental data types will unnecessarily distribute and destroy scalable parallelism.

+6
Feb 14 '10 at 19:19
source share

0 moonlighting as enum

listing features: http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx

as shown in this good example: http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html

my suggestion, put the "@" sign for good use:

instead:

if ((myVar and MyEnumName.ColorRed)! = 0)

use this:

if ((myVar and MyEnumName.ColorRed)! = @ 0)

+5
Jan 05 '09 at 6:31
source share

To add to the long list of good points made by others:

  • DateTime.Now == DateTime.Now in most, but not all cases.

  • String , which is immutable, has many construction and manipulation options, but a StringBuilder (which is modified) does not work.

  • Monitor.Enter and Monitor.Exit should have been instance methods, so instead of entering a specific object to block, you can create a new Monitor and lock it.

  • Destructors should never have been called destructors. The ECMA specification calls them finalizers, which is much less confusing for the C ++ crowd, but the language specification still treats them as destructors.

+5
Jan 09 '09 at 14:02
source share

The way we use properties sometimes annoys me. I like to think of them as the equivalent of the Java getFoo () and setFoo () methods. But this is not so.

If the Rules for Using Properties indicate that the properties should be set in any order for serialization to work, then they are of no use for checking the installation time. If you came from a background where you like to prevent the object from ever allowing itself to fall into an invalid state, properties are not your solution. Sometimes I don’t see how much better they are than public participants, because we are so limited in what we should do in properties.

To this end, I always wanted (mostly out loud here, I just wanted to do something like that) to somehow extend the syntax of the properties. Imagine something like this:

 private string password; public string Password { // Called when being set by a deserializer or a persistence // framework deserialize { // I could put some backward-compat hacks in here. Like // weak passwords are grandfathered in without blowing up this.password = value; } get { if (Thread.CurrentPrincipal.IsInRole("Administrator")) { return this.password; } else { throw new PermissionException(); } } set { if (MeetsPasswordRequirements(value)) { throw new BlahException(); } this.password = value; } serialize { return this.password; } }
private string password; public string Password { // Called when being set by a deserializer or a persistence // framework deserialize { // I could put some backward-compat hacks in here. Like // weak passwords are grandfathered in without blowing up this.password = value; } get { if (Thread.CurrentPrincipal.IsInRole("Administrator")) { return this.password; } else { throw new PermissionException(); } } set { if (MeetsPasswordRequirements(value)) { throw new BlahException(); } this.password = value; } serialize { return this.password; } } 

I am not sure if this will be useful or how he will address them. But I just want me to be able to do more with properties and really treat them like get and set methods.

+4
Jan 05 '09 at 5:33
source share

Extension methods are good, but they are an ugly way to solve problems that could be solved with pure real mixes (look at the ruby ​​to see what I'm talking about) on mixins. A really good way to add them to the language is to allow the use of generics for inheritance. This allows you to extend existing classes using an object-oriented approach:

 public class MyMixin<T> : T { // etc... } 

this can be used, for example, to expand a string, for example:

 var newMixin = new MyMixin<string>(); 

This is a much more powerful tool than extension methods, because it allows you to redefine methods, for example, to wrap them, which allows AOP-like functions inside the language.

Sorry for the conversation :-)

+4
Jan 05 '09 at 10:01
source share

The .Parameters.Add () method in SqlCommand in the V1 structure was terribly designed - one of the overloads would not work if you passed a parameter with a value of (int) 0 - this led to the creation of the .Parameters.AddWithValue () method in class SqlCommand.

+3
Jan 04 '09 at 23:45
source share
  • The ability to call the extension method on the null variable is possible for example.

    object a = null; a.MyExtMethod (); // this is the callee, suppose he defined MyExtMethod somewhere

    It may be convenient, but it is ambiguous in questions of the exclusion of links.

  • One of the names is "flaw." The "C" "configuration" in System.configuration.dll is followed by capital letters.

  • Exception Handling. The exception must be forced to catch or thrown, as in Java, the compiler must check it at compile time. Users should not rely on comments for exception information in the target call.

+3
Jan 04 '09 at 23:48
source share

Microsoft will not correct obvious errors in the structure and will not provide intercepts so that end users can fix them.

In addition, there is no way for executables on the .NET binary patch at run time and in no way specify private versions of the .NET Framework libraries without binary correction of their own libraries (to intercept the load call), and ILDASM is not distributed, so I still can’t automate the patch.

+3
Jan 05 '09 at 3:52
source share
  • There are no subsets of ICollection<T> and IList<T> ; at least a covariant interface for reading only IListSource<out T> (with enumerator, index, and Count) would be extremely useful.
  • .NET does not support weak delegates . Workarounds are awkward at best, and workarounds on the listener side are not possible in partial trust (requires ReflectionPermission).
  • Unified interface unification is prohibited , even if it makes sense and does not cause problems.
  • Unlike C ++, covariance return types are not allowed in .NET
  • Bitwise comparison of two types of values ​​for equality is impossible. In the persistent functional structure, I wrote the Transform(Sequence<T>, Func<T,T>) function Transform(Sequence<T>, Func<T,T>) , which was to quickly determine whether the function returns the same value or a different value. If the function does not change most / all of its arguments, then the output sequence can share some / all of the memory from the input sequence. Without the possibility of bitwise comparison of any value of type T, a much slower comparison should be used, which greatly degrades performance.
  • .NET does not seem to be capable of supporting ad-hoc interfaces (such as those offered by Go or Rust). Such interfaces would make List<T> hypothetical IListSource<U> (where T: U), even if the class does not explicitly implement this interface. There are at least three different libraries (written independently) to provide this functionality (with performance flaws, of course - if there was a great workaround possible, it would be unfair to call this flaw in .NET).
  • Other performance issues: IEnumerator requires two interface calls to iterate. Regular label pointers (open IntPtr delegates) or value delegates (IntPtr * 2) are not possible. Fixed size matrices (of arbitrary type T) cannot be embedded in classes. No WeakReference<T> (you can easily write your own, but it will use throws inside.)
  • The fact that identical types of delegates are considered incompatible (without implicit conversion) was in some cases unpleasant for me (e.g. Predicate<T> vs Func<T,bool> ). I often would like structural typing for interfaces and delegates to achieve weaker communication between components, because in .NET this is not enough for classes in independent DLLs to implement the same interface - they must also share a common link to the third DLL. which defines the interface.
  • DBNull.Value exists, although null would serve the same purpose equally well.
  • C # does not have an operator = =; should you write variable = variable ?? value variable = variable ?? value . Indeed, in C # there are several places that do not need symmetry. For example, you can write if (x) y(); else z(); if (x) y(); else z(); (without curly braces), but you cannot write try y(); finally z(); try y(); finally z(); .
  • When creating a stream, it is not possible to force the child stream to inherit the local values ​​of the stream from the parent stream. BCL not only does not support this, but you cannot implement it yourself unless you create all the threads manually; even if there was a thread creation event, .NET cannot tell you the "parents" or "children" of the given thread.
  • The fact that there are two different length attributes for different data types: “Length” and “graph” is a minor nuisance.
  • I could go on and on with the infinitely bad design of WPF ... and WCF (albeit quite useful for some scenarios) is also full of warts. In general, the bloated, unintuitive, and limited documentation of many of the new BCL libraries makes me reluctant to use them. Many of the new materials could be much simpler, smaller, easier to use and understand, more freely linked, better documented, applicable to more uses, faster and / or more strongly typed.
  • getterter seters: ​​ setter, getter; , ; , getter .
+3
24 . '12 23:58
source share

, 1.x, System.Xml.XmlValidatingReader , ValidationEventHandler ValidationEventArgs XmlSchemaException ( ), , ​​ linenumber position . Message , . , .

+2
09 . '09 13:34
source share

, , :

  enum Colors { white, blue, green, red, black, yellow } enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow } 
+1
27 . '09 7:21
source share

. , Linq, , .

From MSDN:

  • var , ; , .
  • var .
  • , var, . , : int = (i = 20); : var = (i = 20);
  • .
  • var , var .

, , , , var, . , ( , Linq)

0
04 . '09 23:50
source share
  • one
  • 2



All Articles