13

Not a duplicate of this.

I want to make a string have a max length. It should never pass this length. Lets say a 20 char length. If the provided string is > 20, take the first 20 string and discard the rest.

The answers on that question shows how to cap a string with a function but I want to do it directly without a function. I want the string length check to happen each time the string is written to.

Below is what I don't want to do:

string myString = "my long string";
myString = capString(myString, 20); //<-- Don't want to call a function each time

string capString(string strToCap, int strLen)
{
    ...
}

I was able to accomplish this with a property:

const int Max_Length = 20;
private string _userName;

public string userName
{
    get { return _userName; }
    set
    {
        _userName = string.IsNullOrEmpty(value) ? "" : value.Substring(0, Max_Length);
    }
}

Then I can easily use it whout calling a function to cap it:

userName = "Programmer";

The problem with this is that every string I want to cap must have multiple variables defined for them. In this case, the _userName and the userName (property) variables.

Any clever way of doing this without creating multiple variables for each string and at the-same time, not having to call a function each time I want to modify the string?

Community
  • 1
  • 1
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • As well as the answers below, you can use an AOP framework such as PostSharp to define an attribute that intercepts your property setters. PostSharp has a free/express edition. – Rob Lyndon Dec 08 '16 at 21:25
  • Thanks for the suggestion. I am using Unity3D and Mono and trying to prevent using third party libraries that will increase binary size or cause problems on mobile devices. – Programmer Dec 08 '16 at 21:42
  • No idea if it works or not but does Unity respect the [`StringLengthAttribute`](https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.stringlengthattribute(v=vs.110).aspx) built in to .NET? – Scott Chamberlain Dec 08 '16 at 21:42
  • `StringLength` is from the `System.ComponentModel.DataAnnotations` namespace. That is not included or recognized in Unity. It's one of the features Unity did not include in their .NET 3.5. – Programmer Dec 08 '16 at 21:53

2 Answers2

14

Interesting situation - I would suggest creating a struct and then defining an implicit conversion operator for it, similar to what was done in this Stack Overflow question.

public struct CappedString
{
    int Max_Length;
    string val;

    public CappedString(string str, int maxLength = 20)
    {
        Max_Length = maxLength;
        val = (string.IsNullOrEmpty(str)) ? "" :
              (str.Length <= Max_Length) ? str : str.Substring(0, Max_Length);
    }

    // From string to CappedString
    public static implicit operator CappedString(string str)
    {
        return new CappedString(str);
    }

    // From CappedString to string
    public static implicit operator string(CappedString str)
    {
        return str.val;
    }

    // To making using Debug.Log() more convenient
    public override string ToString()
    {
        return val;
    }

    // Then overload the rest of your operators for other common string operations
}

Later you can use it like so:

// Implicitly convert string to CappedString
CappedString cappedString = "newString";

// Implicitly convert CappedString to string
string normalString = cappedString;

// Initialize with non-default max length
CappedString cappedString30 = new CappedString("newString", 30);

Note: This isn't perfect solution, unfortunately - because the implicit conversion doesn't give a way to transfer existing values to the new instance, any CappedString initialized with a non-default length value will need to be assigned to using the constructor, or its length limit will revert back to its default.

Community
  • 1
  • 1
Serlite
  • 12,130
  • 5
  • 38
  • 49
  • +1. I like this better than my answer. (I was looking for a way to have `s = "string"` but didn't think of defining operators.) Good work! – ispiro Dec 08 '16 at 21:20
  • Awesome with the operator overloading + struct too. – Programmer Dec 08 '16 at 21:23
  • I just tried it. I get `ArgumentOutOfRangeException: startIndex + length > this.length` exception. I think you should try it and see for yourself – Programmer Dec 08 '16 at 22:01
  • @Programmer You're right! I was just copying from the original code verbatim, but it looks like it chokes up when the string is shorter than the max length. I edited it to...make an even uglier ternary, so you may or may not want to refactor. =P – Serlite Dec 08 '16 at 22:06
  • @ispiro Thanks for your code. I really like Serlite's solution much more because it uses struct and operator overloading which makes string construction more flexible while conserving memory. – Programmer Dec 08 '16 at 22:18
  • @Serlite The only problem left in your solution now is when you do this: `CappedString userName;` then this `userName = "Programmer Test";`. It doesn't work with `Debug.Log(userName);`. You will get `CappedString` as output. `Debug.Log((string)userName);` fixes it but I can't be doing this each time. Any solution to this? – Programmer Dec 08 '16 at 22:20
  • @Programmer Sure - I edited my question to add an override for `ToString()`, which is what `Debug.Log()` is using when trying to display the object. – Serlite Dec 08 '16 at 22:27
  • There you go. Thanks for your help. Thanks to ispiro too. – Programmer Dec 08 '16 at 22:29
  • When doing `cappedString30 = "…";` afterwards, will it keep the 30 from above or will that create a new instance which defaults back to 20? – Bergi Dec 09 '16 at 01:28
  • 1
    @Bergi The latter, unfortunately. Since C# doesn't allow actual overloading of the assignment operator, this is the best I could think up. Later assignments that require the non-default length limit will still need to explicitly call the constructor, rather than rely on the implicit conversion... – Serlite Dec 09 '16 at 01:56
6

Create a class with a string property, and put all of that code there. Then, you can use s.Value anywhere as a string with the needed characteristic.

Something like:

class Superstring
{
    int max_Length = 20;
    string theString;

    public Superstring() { }
    public Superstring(int maxLength) { max_Length = maxLength; }
    public Superstring(string initialValue) { Value = initialValue; }
    public Superstring(int maxLength, string initialValue) { max_Length = maxLength; Value = initialValue; }

    public string Value { get { return theString; } set { theString = string.IsNullOrEmpty(value) ? value : value.Substring(0, Math.Min(max_Length, value.Length)); } }
}

and use:

Superstring s = new Superstring("z");
s.Value = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
string s2 = s.Value;
ispiro
  • 26,556
  • 38
  • 136
  • 291
  • Hoping to avoid creating another class instance that holds the main string. That's multiple steps and will take more memory than the code in my question. Will wait to see if there is another way. – Programmer Dec 08 '16 at 21:08