Monday, February 11, 2013

Garbage free strings in C#

This is a hack (It only works with reflection) to let you achieve garbage free string concatenation in C#, Mono:


using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

    public class StringReference
    {
        public int MaxSize
        {
            get;
            private set;
        }
        public int SpaceLeft
        {
            get { return MaxSize - StringLength; }
        }
        public int StringLength
        {
            get { return StringBuilder.Length; }
        }
        public string StringHandle
        {
            get;
            private set;
        }
        public StringBuilder StringBuilder
        {
            get;
            private set;
        }
       

        public StringReference(int maxSize=32)
        {
            Resize(maxSize);
        }
       
       
        public void Resize(int maxSize)
        {
            MaxSize = maxSize;
            StringBuilder = new StringBuilder(maxSize, maxSize);
           
            var initialized = false;
           
            try
            {
                // This should work in Mono (Unity3D)
                var typeInfo = StringBuilder.GetType().GetField("_str", BindingFlags.NonPublic | BindingFlags.Instance);
                if(typeInfo != null)
                {
                    StringHandle = (string)typeInfo.GetValue(StringBuilder);
                    initialized = true;
                }
            }
            catch
            {
               
                try
                {
                    // This might work on a .NET platform
                    var typeInfo = StringBuilder.GetType().GetField("_cached_str", BindingFlags.NonPublic | BindingFlags.Instance);
                    if (typeInfo != null)
                    {
                        StringHandle = (string)typeInfo.GetValue(StringBuilder);
                        initialized = true;
                    }
                }
                catch
                {
                    throw new Exception("Can't get access to StringBuilders internal string.");
                    /*
                     * Uncomment this section to get a clue on how to get a reference to the underlying string:
                     *
                    Type t = StringBuilder.GetType();
                    foreach (var f in t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
                        UnityEngine.Debug.Log(f.Name);
                    */
                }
               
            }
        }
       
        public void Clear()
        {
            StringBuilder.Remove(0, StringBuilder.Length);
        }
       
        public void SetText(string text, bool fillOverflow = true)
        {
            SetText(ref text, fillOverflow);
        }
        public void SetText(ref string text, bool fillOverflow = true)
        {
            Clear();
           
            var max = SpaceLeft;
            if (text.Length >= SpaceLeft)
            {
                StringBuilder.Append(text, 0, max);
            }
            else
            {
                StringBuilder.Append(text);
                if(fillOverflow) FillOverflow();
            }
        }
        public void Append(ref string text)
        {
            var max = SpaceLeft;
            if (text.Length >= SpaceLeft)
                StringBuilder.Append(text, 0, max);
            else
                StringBuilder.Append(text);
        }
        public void Append(string text)
        {
            Append(ref text);
        }
       
        public void FillOverflow(char character = ' ')
        {
            var overflow = SpaceLeft;
            if (overflow > 0)
                StringBuilder.Append(character, overflow);
        }
    }



How to use:

StringReference str = null;
str = new StringReference(128);
str.StringBuilder.Append("This is an example of garbage-");
str.StringBuilder.Append("free string concatenation.");
Debug.Log(str.StringHandle);

No comments:

Post a Comment