This project is read-only.

Please consider class String<T> : StringValue

Topics: C# Language Design, General, VB Language Design
Jul 11, 2014 at 2:56 AM
class String<T> : StringValue where T : StringValueDescriptor, new() {…}

internal class CustomerAO : AuditableAO, Customer {
    public CustomerAO() {
        Id = Guid<GuidTypes.Customer>.Default;
        CustomerNumber = String<Length50>.Empty;
    }

    public Guid<GuidTypes.Customer> Id { get; set; }
    public String<Length50> CustomerNumber { get; set; }
}
Jul 11, 2014 at 1:23 PM
StewartScottRoger wrote:
class String<T> : StringValue where T : StringValueDescriptor, new() {…}

internal class CustomerAO : AuditableAO, Customer {
    public CustomerAO() {
        Id = Guid<GuidTypes.Customer>.Default;
        CustomerNumber = String<Length50>.Empty;
    }

    public Guid<GuidTypes.Customer> Id { get; set; }
    public String<Length50> CustomerNumber { get; set; }
}
It sounds like you want attributes, not generics. Unless you're proposing that a Type be added for every potential length of a string, and that's just ludicrous. As for the Guid thing, I have no idea as to your intention there except perhaps to claim that a Guid identifier for one entity can't be used as a Guid identifier for another, which just seems silly. Not to mention, generics don't work with enum values like that. Again, you'd have to define a different Type for each of the potential entity types.

There's nothing stopping you from writing your own Guid<T> and String<T> types. The compiler will happily enforce the constraints due to type invariance. I don't think it's going to work quite the way that you're expecting it to, though.
Jul 11, 2014 at 3:42 PM
Edited Jul 11, 2014 at 3:57 PM
I have implemented the String<T> with great difficulty due to the sealing of the String class. It can be successfully implemented with performance draw backs.

I am an unabashed type bigot. Strong typing is the backbone of enterprise scale projects. Let me elaborate Something like Domain Driven Design with Multiple Data Access Layers being consumed by a domain and its BLL that presents methods and contracts to multiple transport layers. Transport objects mapped and presented across networks to application service layers that in term maps and provides application objects to a presentation layer. {DO -> DTO -> AO} When implementing large projects like this the persistence layer within the DAL is usually a database with string representation like “varchar 50” in a reasonably designed application there will be some limited number of string length used in the database not the entire possible range of all string lengths.

These large scale projects are not the norm in the industry. Most projects are Web Applications setting on top of a simple domain that accesses a database maybe two; this is ASP.NET’s bread and butter project space. I am discussing much larger projects with multi decade life expectancies that require modification not replacement in their application life cycle.

Now that I have framed why this need for a strongly typed string of a fixed length and thus a generic implementation, I will address why it is needed in the Compiler rather than as a strict BCL feature. The String Class in the BCL is neither a true Heap Object or Stack Value like all other data in a .NET application; it is a hybrid due to performance and usability attributes built into .NET from the beginning and as such resistant to leveraging outside the compiler-BCL entanglement. If the String were to be re-implemented as a generic with <LengthXXXXXX> type descriptor as the base type of the ‘System.String’ and ‘string syntactic sugar’ then the performance issues could be addressed by the compiler team and the BCL team as a joint effort just as the original the ‘System.String’ and ‘string syntactic sugar’ must have been. The original ‘System.String’ would then be just a subclass of String<MAXLENGTH> generic and should be backwards compatible.

To reiterate highly layered and strongly typed solution spaces could benefit from the reliability of String length enforcement as a generic type of String<LengthXXXX> rather than having invalid Strings bouncing off database inserts/update or truncating that insert/update at the database or other strongly typed persistence layer. In addition having a strongly typed length will be exposed at compile time rather than runtime using reflection as a cycle consuming workaround. An attribute must be intercepted at runtime using reflection (Many-Many-Cycles over and over again verse one compiler enforced type check)

The Presentation Layer will benefit greatly by not permitting the user to enter more characters than are implemented by the Persistence layer or other length limited resources rather than telling the user after the fact that the length was too long. If implemented his would greatly benefit many project(s) reliability and cost of testing over the full lifecycle of the application.
Jul 11, 2014 at 5:02 PM
It sounds like what is really desired is an ability to declare a limited form of inheritance which would allow one to declare a type which could extend even a sealed type; one could specify that two variables of different "extension type" could opt to allow implicit convert to/from the base type (and would actually be references to base-type instances) but not to each other. Such a thing could be implemented I think entirely as a compiler concept, and could be very useful if such types could include extension-method and operator overloads [an extension type would be a static class; storage locations declared to be of the extension type would be stored as references to the "base" type, but the compiler would apply the specified extension methods, conversions, and operator overloads according to what was defined within the extension type].

Thus, for example, one could define FilePathString and XMLString as "extensions" of String, and specify that both should be stored as String, and either should implicitly convert to String, but specify that only FilePathString should accept implicit conversions from String [compilers that understand "extension-type" attributes would require a typecast for conversions from String to XMLString]. Further, FilePathString could host extension methods like GetLastPathComponent which other kinds of string would not.

Perhaps you're after something else, but it sounds like your real interest is to allow the creation of a set of types which can be used in place of String, but which the compiler could recognize as being different. If that is the case, then rather than adding a generic type parameter to String, I would suggest a more general-purpose facility, implemented at the compiler level, might be more helpful.
Jul 11, 2014 at 6:34 PM
Inheritance definitely seems like the wrong way to solve this problem. Could you solve your problem through encapsulation, rather than inheritance?
public class FixedLengthString
{
    private readonly int _maxLength;

    public String Value {get { return _value; } set { if(value.Length > _maxLength) throw new ArgumentException(); _value = value; }}
    private String _value;

    protected FixedLengthString(int maxLength)
    {
         _maxLength = maxLength;
    }
}
Then, when you actually need a specific class, you inherit from this:
public class BusinessName : FixedLengthString
{
     public BusinessName() : base(50) { }
}
Jul 12, 2014 at 12:16 AM
I've also worked on such projects and never saw the need for such implementation.

What you need is metadata and that can be achieved with .NET attributes.

If your metadata changes too often (the length of the name of the customer to today is 50, last year it was 30), then you should design your system to accommodate that.

Most web UIs are becoming more interactive and that means more client side code. Are you also asking the ECMAScript comity to add such features to it?

Fixed length data exists only because systems can't hold/transmit/process/show infinite amounts of data.

An Int32 has 4 bytes. That information is on the type, not in every instance of Int32.

Now, one could ask for a .NET standard way to enforce contracts, but setting that in stone would kill evolution and choice.
Jul 15, 2014 at 5:43 PM
PauloMorgado wrote:
Fixed length data exists only because systems can't hold/transmit/process/show infinite amounts of data.
Fixed-length data can also support mutable value semantics efficiently. If a class will frequently need to create and hold a slightly-modified version of a fixed-size structure which is passed to it, it can simply take its copy of the structure and make the appropriate change. If code endeavors to pass structures by ref whenever possible, there is almost no limit to the size of structure which can be processed efficiently. By contrast, if the information were not of fixed size, it would be necessary for the class to create a new heap object which would holding a modified version of the information it received; the more information the object contains, the greater the penalty for having to copy it to a new heap object.
Jul 15, 2014 at 8:28 PM
supercat wrote:
PauloMorgado wrote:
Fixed length data exists only because systems can't hold/transmit/process/show infinite amounts of data.
Fixed-length data can also support mutable value semantics efficiently.
But this isn't about fixed-size mutable structs, it's about fixed-size (and still immutable) strings.
Jul 16, 2014 at 4:39 AM
Edited Jul 16, 2014 at 4:40 AM
svick wrote:
supercat wrote:
PauloMorgado wrote:
Fixed length data exists only because systems can't hold/transmit/process/show infinite amounts of data.
Fixed-length data can also support mutable value semantics efficiently.
But this isn't about fixed-size mutable structs, it's about fixed-size (and still immutable) strings.
Yes that’s the Idea a fixed-size or String<size> as the base of the current ‘string’ sugar where “public class String: String<MaxSize>{}” replaces the current String implementation in the BCL. The compiler would have to be adjusted to use the new base class of String<Size> and the ‘string’ sugar extended with something like ‘string50’ or some such syntax to build it into the compiler so the CLR has the equivalent of “String<Length50>” generated as declared by the sugar.

I would not want to create Length1 to Length9999 of <size>.. as this would be excessive but the compiler could generate it as needed. This does bring about the need for duck typing between different projects within a solution, but maybe the compiler guys would have a better work around.

Basically the current implementation of 'String' with its single and very large size would need to be extended to have 1 to N length Strings each with their own distinct size and enforced typing. Only explicit casting would let you Cast one length String into another. There could even be compiler enforced rules such as that between Int32 and Int64. No data loss if you put the value of Int32 into Int64 but the other way around could be a possible overflow exception. same for String<Length50> and String<Length7>.
Jul 16, 2014 at 4:56 AM
Edited Jul 16, 2014 at 5:15 AM
MgSam wrote:
Inheritance definitely seems like the wrong way to solve this problem. Could you solve your problem through encapsulation, rather than inheritance?
public class FixedLengthString
{
    private readonly int _maxLength;

    public String Value {get { return _value; } set { if(value.Length > _maxLength) throw new ArgumentException(); _value = value; }}
    private String _value;

    protected FixedLengthString(int maxLength)
    {
         _maxLength = maxLength;
    }
}
Then, when you actually need a specific class, you inherit from this:
public class BusinessName : FixedLengthString
{
     public BusinessName() : base(50) { }
}
The difference between what I am purposing and your counter example is evident in two ways.
  1. Your length enforcement occurs at run-time and the solution I am suggestion occurs at compile time. The compiler will enforce quality of code while yours will require unit tests. Any time I can use my compiler as the unit test the gains in project through put are huge.
  2. My run-time efficiency should be at unity with the current implementation as the BCL ‘String’ class. Your implementation would not. Your implementation would require writing error handlers to deal with the run-time exceptions you would be throwing. The implementation I am proposing would make the error handling that currently exists in DALs to handle String-To-Big type exception unneeded., because it could not happen {VarChar 50 == String<Length50>} there would be no way of building a string length greater than 50.
Jul 16, 2014 at 5:14 AM
supercat wrote:
It sounds like what is really desired is an ability to declare a limited form of inheritance which would allow one to declare a type which could extend even a sealed type; one could specify that two variables of different "extension type" could opt to allow implicit convert to/from the base type (and would actually be references to base-type instances) but not to each other. Such a thing could be implemented I think entirely as a compiler concept, and could be very useful if such types could include extension-method and operator overloads [an extension type would be a static class; storage locations declared to be of the extension type would be stored as references to the "base" type, but the compiler would apply the specified extension methods, conversions, and operator overloads according to what was defined within the extension type].

Thus, for example, one could define FilePathString and XMLString as "extensions" of String, and specify that both should be stored as String, and either should implicitly convert to String, but specify that only FilePathString should accept implicit conversions from String [compilers that understand "extension-type" attributes would require a typecast for conversions from String to XMLString]. Further, FilePathString could host extension methods like GetLastPathComponent which other kinds of string would not.

Perhaps you're after something else, but it sounds like your real interest is to allow the creation of a set of types which can be used in place of String, but which the compiler could recognize as being different. If that is the case, then rather than adding a generic type parameter to String, I would suggest a more general-purpose facility, implemented at the compiler level, might be more helpful.
I think you get it The Compiler(s) and the BCL need to extend or modify the "String" that we all know and love as 1-to-N strongly typed 'stringLLL' types that would be just as available as 'string'. Imagine how easy and slick hydration and dehydration of objects to and from a database would become. Imagine a TextBox or any input control limiting the length of the input the user types because it is typed to a correctly sized string; and your compiler will not let you screw it up.
Jul 16, 2014 at 5:19 AM
Halo_Four wrote:
StewartScottRoger wrote:
class String<T> : StringValue where T : StringValueDescriptor, new() {…}

internal class CustomerAO : AuditableAO, Customer {
    public CustomerAO() {
        Id = Guid<GuidTypes.Customer>.Default;
        CustomerNumber = String<Length50>.Empty;
    }

    public Guid<GuidTypes.Customer> Id { get; set; }
    public String<Length50> CustomerNumber { get; set; }
}
It sounds like you want attributes, not generics. Unless you're proposing that a Type be added for every potential length of a string, and that's just ludicrous. As for the Guid thing, I have no idea as to your intention there except perhaps to claim that a Guid identifier for one entity can't be used as a Guid identifier for another, which just seems silly. Not to mention, generics don't work with enum values like that. Again, you'd have to define a different Type for each of the potential entity types.

There's nothing stopping you from writing your own Guid<T> and String<T> types. The compiler will happily enforce the constraints due to type invariance. I don't think it's going to work quite the way that you're expecting it to, though.
There is another thread for the "Guid<T>" would you like to talk about this there. The threads are related but maybe simpler to deal with it in the other thread
Jul 16, 2014 at 5:42 AM
PauloMorgado wrote:
I've also worked on such projects and never saw the need for such implementation.

What you need is metadata and that can be achieved with .NET attributes.

If your metadata changes too often (the length of the name of the customer to today is 50, last year it was 30), then you should design your system to accommodate that.

Most web UIs are becoming more interactive and that means more client side code. Are you also asking the ECMAScript comity to add such features to it?

Fixed length data exists only because systems can't hold/transmit/process/show infinite amounts of data.

An Int32 has 4 bytes. That information is on the type, not in every instance of Int32.

Now, one could ask for a .NET standard way to enforce contracts, but setting that in stone would kill evolution and choice.
I don't think strong types and JavaScript can be said in the same sentence. I would not ask the ECMAScript comity to do it, but I might suggest it to the TypeScript team because their entire project tries to make JavaScript strongly typed; Anders Hejlsberg has commented on why this is a good thing. I am more interested in getting C# polished up as it enters midlife and we have an opensource compiler that can actually be worked on by its users. It may be several years before a mod like this could be scheduled. The team has its agendas and this would not be on the road map as they have published it.
Jul 16, 2014 at 6:17 AM
Edited Jul 16, 2014 at 6:25 AM
I think what you guys are talking about are dependant types. This is a known area of reasearch at Microsoft and addresses the problems you are talking about.

http://research.microsoft.com/apps/pubs/?id=141708

There is a prototype implementation called Fstar / F* which is an ML dialect ( similar to F# )

http://research.microsoft.com/en-us/projects/fstar/

You can play around with a web live version here

http://rise4fun.com/FStar

Don't expect to see this is a Visual Studio IDE near you any time soon :)

For example this code fails the type checker
type zero = x:int{x=0}

let x : zero = 1
and this code passes
type zero = x:int{x=0}

let x : zero = 0
even cooler
type zero = x:int{x=0}
type one =  x:int{x=1}

let x : zero = 0
let y : one = x + 1
passes the type checker
type zero = x:int{x=0}
type one =  x:int{x=1}

let x : zero = 0
let y : zero = x + 1
fails the type checker

I could see this being extended to strings of a specific length.
Jul 16, 2014 at 6:51 AM
StewartScottRoger wrote:
svick wrote:
supercat wrote:
PauloMorgado wrote:
Fixed length data exists only because systems can't hold/transmit/process/show infinite amounts of data.
Fixed-length data can also support mutable value semantics efficiently.
But this isn't about fixed-size mutable structs, it's about fixed-size (and still immutable) strings.
Yes that’s the Idea a fixed-size or String<size> as the base of the current ‘string’ sugar where “public class String: String<MaxSize>{}” replaces the current String implementation in the BCL. The compiler would have to be adjusted to use the new base class of String<Size> and the ‘string’ sugar extended with something like ‘string50’ or some such syntax to build it into the compiler so the CLR has the equivalent of “String<Length50>” generated as declared by the sugar.

I would not want to create Length1 to Length9999 of <size>.. as this would be excessive but the compiler could generate it as needed. This does bring about the need for duck typing between different projects within a solution, but maybe the compiler guys would have a better work around.

Basically the current implementation of 'String' with its single and very large size would need to be extended to have 1 to N length Strings each with their own distinct size and enforced typing. Only explicit casting would let you Cast one length String into another. There could even be compiler enforced rules such as that between Int32 and Int64. No data loss if you put the value of Int32 into Int64 but the other way around could be a possible overflow exception. same for String<Length50> and String<Length7>.
I see the errors of my ways “public class String: String<MaxSize>{}”. If I did this the entire compiled BCL API would need to be recompiled... not good. I would not inherit String from String<Lenght>. The String<Length> Would need to be an addition to the BCL with an implicit cast to "String" but not the other way around.
Jul 16, 2014 at 7:10 AM
Edited Jul 16, 2014 at 7:11 AM
bradphelan wrote:
I think what you guys are talking about are dependant types. This is a known area of reasearch at Microsoft and addresses the problems you are talking about.

http://research.microsoft.com/apps/pubs/?id=141708

There is a prototype implementation called Fstar / F* which is an ML dialect ( similar to F# )

http://research.microsoft.com/en-us/projects/fstar/

You can play around with a web live version here

http://rise4fun.com/FStar

Don't expect to see this is a Visual Studio IDE near you any time soon :)

For example this code fails the type checker
type zero = x:int{x=0}

let x : zero = 1
and this code passes
type zero = x:int{x=0}

let x : zero = 0
even cooler
type zero = x:int{x=0}
type one =  x:int{x=1}

let x : zero = 0
let y : one = x + 1
passes the type checker
type zero = x:int{x=0}
type one =  x:int{x=1}

let x : zero = 0
let y : zero = x + 1
fails the type checker

I could see this being extended to strings of a specific length.
Thanks for turning me on to this http://en.wikipedia.org/wiki/Dependent_type. That is the idea. At least I now know it can be done... I don't think it is as complicated a problem as this describes. I just need an addition to the BCL for String<LenghX>, some syntactic sugar 'stringX' in the compiler and an implicit cast from 'String<LenghX>' to 'String.' maybe Microsoft could consider this kind of Dependent type:String Length in the next couple of years. I implemented this in a generic class in a previous Domain Driven Design and it cleaned up so many problems. Its only draw back was processing speed. I could not stream a query of objects of any size without paying a performance hit that was two excessive. Normal CRUD was fast enough and overall load was not an issue so it worked. However, to be useful across the broad range; a compiler based solution that could handle a Generic and/or Dependent type String would be needed to have performance characteristics on power with the current sealed String class.
Jul 16, 2014 at 2:12 PM
Edited Jul 16, 2014 at 2:14 PM
Well you could if you wanted to define numbers as a type with the following trick
interface IDigit {
   int Value { get; }
}
class _0 : IDigit { int Value { get { return 0;} } }
class _1 : IDigit { int Value { get { return 1;} } }
...
class _9 : IDigit { int Value { get { return 9;} } }

interface ILength {
    int Value { get; }
}
class Length<T0> : ILength where T0 : IDigit, new(){
        int Value { get { return new T0().Value; }}
}
class Length<T1,T0> : ILength where T0 : IDigit, new() where T1 : IDigit, new(){
        int Value { get { return  new T1().Value * 10 + new T0().Value; }}
}
class Length<T2,T1,T0> : ILength where T0 : IDigit new() where ..... {
        int Value { get { return new T2().Value * 100 + new T1().Value * 10 + new T0().Value();}}
}

class String<TLength> : where TLength : ILength, new() {
    int _MaxLength;
    public String(){
        _MaxLength = new TLength().Value;
    }
}
then I can do define a string of length 768 by
String<Length<_7,_6,_8>> string768 = new String<Length<_7,_6,_8>>();
Seems like a total abuse of the type system but it might work and I'm sure I've overlooked something obvious that will make it not compile.
Jul 16, 2014 at 2:25 PM
And just for giggles I wrote and compiled and tested the above idea. Works fine.

https://gist.github.com/bradphelan/26c0e84197092620359a
        public void LengthCheck(String<Length<_3, _1, _2>> s312)
        {
            Console.WriteLine(s312._MaxLength);
        }

        [Fact]
        public void FactMethodName()
        {
            var s312 = new String<Length<_3, _1, _2>>();
            var s313 = new String<Length<_3, _1, _3>>();
 
            // Passes
            s312._MaxLength.Should().Be(312);
 
            // Type checks
            LengthCheck(s312);
 
            // Fails to type check
            LengthCheck(s313);
 
        }
Jul 19, 2014 at 6:24 PM
Actually, one can do even better. Given an interface
interface GenericAction<TItem, TParam, TResult> 
{ 
  TResult Act<T>(ref TParam param, TResult result) where TList:IList<TItem>;
}
it's possible to--without using Reflection and without boxing--write a method:
TResult CallActionWithIList<TItem, TParam, TResult>(
  GenericAction<TItem, TParam, TResult> action, 
  ref TParam param,
  int n)
{...}
which will call the indicated action with a structure type that implements IList<TItem> and can hold the specified number of items. If one didn't mind rounding having the size of the IList<T> rounded up to the next power of two, the IList<T> could be implemented using structures [simplified slightly]
delegate ActByRef<T1,T2> { void Invoke(ref T1 p1, ref T2 p2); }
interface IListPlusAct<T> : IList<T> { void ActOnItem<TExtra>(int index, ActByRef<T, TExtra> proc, ref TExtra extra); }
structure SingleItem<T>: IListPlusAct<T> {
  T item;
  public T this[index] {
    get {if (index != 0) throw new ArgumentOutOfRangeException(); else return item; }
    set {if (index != 0) throw new ArgumentOutOfRangeException(); else item = value; }
  }
  public void ActOnItem(int index, ActByRef<T, TExtra> proc, ref extra)
  {
    if (index != 0) throw new ArgumentOutOfRangeException(); 
    proc(ref item, ref extra);
  }
}
structure MatchedPairOfList<T>: IList<T> where T:IList<T>
{
  T first, second;

  public T this[index] {
    get {
      T result;
      if ((index & 1)==0) 
        first.ActOnItem(index/2, (ref T it, ref T dest) => dest=it, ref result);
      else
        second.ActOnItem(index/2,  (ref T it, ref T dest) => dest=it, ref result);
      return result;
    }
    set {
      if ((index & 1) == 0)
        first.ActOnItem(index/2, (ref T it, ref T src) => it=src, ref value);
      else
        first.ActOnItem(index/2, (ref T it, ref T src) => it=src, ref value);
    }
  }
  public void ActOnItem(int index, ActByRef<T, TExtra> proc, ref extra)
  {
    if ((index & 1) == 0)
      first.ActOnItem(index/2, proc, ref extra);
    else
      second.ActOnItem(index/2, proc, ref extra);
  }
}
A MatchedPair<MatchedPair<MatchedPair<MatchedPair<MatchedPair<MatchedPair<MatchedPair<MatchedPair<SingleItem<System.Drawing.Point>>>>>>>>> would behave as a 256-item list of points, all held within a structure; any desired power-of-two size could be achieved simply by nesting. Note that because of the fact that no mechanism exists for properties of structure type to expose an lvalue, it's necessary for the interface to include an ActOnItem method which does expose the encapsulated list items as lvalues. This would allow code to add newX to myPoints[26].X "in-place" via:
ActOnItem(26, (ref Point it, ref int src) => it.X += src, ref newX);
without any need for boxing, closures, or any redundant copying of list items. The runtime efficiency of these methods isn't great (each access requies lg(N) recursive calls) but it avoids any O(N) behaviors. While it would have been simpler to write the property getter of MatchedPairOfList as
if (((index & 1)==0)
  return first[index/2];
else
  return second[index/2];
that approach would have required the outermost call on a 256-item list to receive from the inner call a copy of half of the list items (i.e. 128 of them), even though it was only interested in one. Not very efficient. Using the ActOnItem method makes it possible to avoid any redundant copy operations.
Jul 19, 2014 at 11:21 PM
StewartScottRoger wrote:
Now that I have framed why this need for a strongly typed string of a fixed length and thus a generic implementation, I will address why it is needed in the Compiler rather than as a strict BCL feature. The String Class in the BCL is neither a true Heap Object or Stack Value like all other data in a .NET application; it is a hybrid due to performance and usability attributes built into .NET from the beginning and as such resistant to leveraging outside the compiler-BCL entanglement. If the String were to be re-implemented as a generic with <LengthXXXXXX> type descriptor as the base type of the ‘System.String’ and ‘string syntactic sugar’ then the performance issues could be addressed by the compiler team and the BCL team as a joint effort just as the original the ‘System.String’ and ‘string syntactic sugar’ must have been. The original ‘System.String’ would then be just a subclass of String<MAXLENGTH> generic and should be backwards compatible.

To reiterate highly layered and strongly typed solution spaces could benefit from the reliability of String length enforcement as a generic type of String<LengthXXXX> rather than having invalid Strings bouncing off database inserts/update or truncating that insert/update at the database or other strongly typed persistence layer. In addition having a strongly typed length will be exposed at compile time rather than runtime using reflection as a cycle consuming workaround. An attribute must be intercepted at runtime using reflection (Many-Many-Cycles over and over again verse one compiler enforced type check)
It's unclear to me how the compiler is going to be able to check the length of strings that are being assigned to one of these length-constrained types via user input or other forms of I/O. I guess I can vaguely see how a String<Length32> could be limited to not accept concatenations of String<N> where the total length is greater than 32, but this seems like a rather narrow target for a proposed language feature. You would still require UI frameworks, ORMs, serializers and other string-handling code to perform runtime checks or alter runtime behaviour based on the generic parameter in the way that they currently do based on attributes.

Looking at the Wikipedia article on Dependent Types, they seem to be popular in purely functional languages, where I could see the benefit. But in a general-purpose language like C#, used for application building and the like, I don't see the value in this.