Skip to main content

ConditionalWeakTable, what does it do?

C#'s ConditionalWeakTable, what does it do?

C# has many lesser known features, some more useful than others, and one of them is the ConditionalWeakTable<TKey, TValue> (keep in mind that TKey and TValue must be reference types).
You can think of this type as a dictionary where the keys are weakly referenced, meaning that they won’t count when the GC checks if the object has to be collected. Additionally, when the keys do eventually get collected by the GC, that entry will get removed from the dictionary. This means that you can attach arbitrary objects to any object, allowing you to do something like this:

public static class Extensions
{
    private static ConditionalWeakTable<object, dynamic> Table = new ConditionalWeakTable<object, dynamic>();

    public static dynamic Data(this object obj)
    {
        if (!Table.TryGetValue(obj, out var dyn))
            Table.Add(obj, dyn = new ExpandoObject());

        return dyn;
    }
}

...

var myObject = "hello";
myObject.Data().Foo = "bar";

Assert.AreEqual(myObject.Data().Foo, "bar");

As you can see, this is a really powerful feature allowing you to add any data you want to any object, although it may also lead to some ugly code so beware! If you’re still not convinced, take a look at a StringBuilder extension class I built for a project:

public static class StringBuilderExtensions
{
    private class StringBuilderData
    {
        public int Indentation;
    }

    private static ConditionalWeakTable<StringBuilder, StringBuilderData> DataTable = new ConditionalWeakTable<StringBuilder, StringBuilderData>();

    public static StringBuilder AppendIndentation(this StringBuilder builder)
    {
        return builder.Append(new string(' ', DataTable.GetOrCreateValue(builder).Indentation * 4));
    }

    public static StringBuilder Indent(this StringBuilder builder)
    {
        DataTable.GetOrCreateValue(builder).Indentation++;
        return builder;
    }

    public static StringBuilder Unindent(this StringBuilder builder)
    {
        DataTable.GetOrCreateValue(builder).Indentation--;
        return builder;
    }
}

Which you would use like:

var builder = new StringBuilder();

builder.AppendIndentation().AppendLine("This is unindented")
       .Indent()
       .AppendIndentation().AppendLine("This is indented by one level")
       .Unindent()
       .AppendIndentation().AppendLine("This is unindented again");

Yes, you could just write your own StringBuilder-esque class, but where’s the fun in that!

This type is clearly not meant for day-to-day use but I’m sure there’s at least one situation where using this type is the best solution. One place where I think it could be useful is if you’re patching methods at runtime with something like pardeike’s Harmony and you want to store additional info on a patched method’s instance object.

Comments

  1. Great! But why we couldn't do the same with a standard dictionary? You said it is related to GC but I don't see the problem

    ReplyDelete
    Replies
    1. Because a regular dictionary holds a strong reference to the values and, most importantly in this case, keys. So if you finish using an object everywhere in your program, the dictionary will still hold a reference to it, preventing the GC from collecting it. This is very problematic as objects will keep piling up and using memory.

      Delete

Post a Comment

Popular posts from this blog

C# to JS: LINQ Expressions and ExpressionVisitor

How to translate (some) C# to JavaScriptIf you’ve ever used something like EF Core you may have noticed that methods like LINQ’s Where take an Expression<Func<...>> as the first argument, instead of simply a Func<...>:
Expressions were introduced in .NET 3.5 along with the rest of LINQ and they represent the code tree that a lambda is composed of. For example, if you had a lambda like () => a + b, this is how the resulting expression tree would look like:
.NET 4.0 later introduced ExpressionVisitor, which lets you traverse the expression tree. Take this visitor for example:publicclassConsolePrinterVisitor: ExpressionVisitor {//This is just a helper method to get the Expression, normally you would already have it//so you'd just do ConsolePrinterVisitor.Visit(expression)publicvoidWriteLambda(Expression<Func<object>> expression){this.Visit(expression);}protectedoverride Expression VisitBinary(BinaryExpression node){ Console.Write("("…

Building LINQ Expressions: How to run dynamic code at runtime (part 1)

How to run dynamic code at runtime (part 1)In my previous post I talked about how to read LINQ expressions using an ExpressionVisitor, however reading isn’t the only thing you can do with expressions: you can build them at runtime!If you’ve ever used reflection you’re probably aware that you shouldn’t use them in performance-critical situations as they’re one of the slowest parts of .NET, but what if you desperately need to use it?Enter the world of expression-building. Before I show you how anything works, I want you to compare speeds: RawAccess: 00:00:00.0000344 b*1,0 (3,44E-06ms per iteration) ReflectionNoCache: 00:00:00.0018118 b*52,7 (0,00018118ms per iteration) ReflectionWithCache: 00:00:00.0009518 b*27,7 (9,518E-05ms per iteration) ExpressionNoCache: 00:00:00.9333534 b*27132,4 (0,09333534ms per iteration) ExpressionWithCache: 00:00:00.0000424 b*1,2 (4,24E-06ms per iteration) Benchmark ran with .NET Core 2.2 on an AMD Ryzen 2400G @ 3.85Ghz and Windows 10.As you can …