question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Refactor/Remove `PropertyStore`

See original GitHub issue

Background and motivation

We have had a few bugs caused by incorrect usage of PropertyStore. It’s clunky and we can use private backing fields instead. This would allow nullable annotations and compile time checks which would have prevented #8990

Also can look into using a dictionary instead to gain perf improvements. But

Issue raised based on comment: https://github.com/dotnet/winforms/pull/9503#pullrequestreview-1533416827

API Proposal

internal class PropertyStore
    {
        private sealed class ValueWrapper<T> where T : struct
        {
            public T Value { get; }

            public ValueWrapper(T value)
            {
                Value = value;
            }
        }

        private readonly Dictionary<int, object?> properties = new();

        private static int s_currentKey = 0;

        public static int CreateKey() => s_currentKey++;

        public void SetValue<T>(int key, T value) where T : struct
        {
            properties[key] = new ValueWrapper<T>(value);
        }

        public void SetObject<T>(int key, T value)
        {
            properties[key] = value;
        }

        public T GetValue<T>(int key) where T : struct
        {
            if (properties.TryGetValue(key, out var value) && value is ValueWrapper<T> wrapper)
            {
                return wrapper.Value;
            }

            return default;
        }

        public T? GetObject<T>(int key)
        {
            return properties.TryGetValue(key, out var value) && value is T tValue ? tValue : default;
        }

        public bool TryGetValue<T>(int key, out T? value) where T : struct
        {
            if (properties.TryGetValue(key, out var objValue) && objValue is ValueWrapper<T> wrapper)
            {
                value = wrapper.Value;
                return true;
            }

            value = default;
            return false;
        }

        public bool TryGetObject<T>(int key, out T? value) where T : class
        {
            if (properties.TryGetValue(key, out var objValue) && objValue is T tValue)
            {
                value = tValue;
                return true;
            }

            value = null;
            return false;
        }

        public bool Remove(int key) => properties.Remove(key);
    }

API Usage

N/A

Alternative Designs

No response

Risks

It is a large refactor and could cause subtle bugs. The current design is meant for performance reasons (I don’t understand why?).

Will this feature affect UI controls?

Controls will need testing.

Issue Analytics

  • State:open
  • Created 2 months ago
  • Reactions:1
  • Comments:12 (12 by maintainers)

github_iconTop GitHub Comments

2reactions
JeremyKuhnecommented, Jul 21, 2023

@elachlan ValueWrapper<T> is more trouble than it is worth. I was thinking of going as simple as possible:

internal class PropertyStore
{
    // Should set an appropriate capacity- what is typical for our controls?
    private readonly Dictionary<int, object?> _properties = new();

    private static int s_currentKey = 0;

    // What is our typical ratio of keys to used values? How much memory are we really saving?
    // Dictionary<TKey, TValue> is eating at least 12-16 extra bytes for everything we put in it.
    // We'd need to be something like 1 used value to 8 keys for this stuff to make any sense.
    //
    // We could potentially use a collection with less memory overhead, but that comes at significant
    // cost in additional complexity.
    //
    // If we can demonstrate that we don't save much memory, we're likely better off removing the
    // PropertyStore completely and utilizing fields. We can look at our field usages and optimize there.
    public static int CreateKey() => Interlocked.Increment(ref s_currentKey);

    public void SetValue(int key, object? value) => _properties[key] = value;
    public bool RemoveValue(int key) => _properties.Remove(key);

    public bool TryGetValue<T>(int key, out T? value)
    {
        if (!_properties.TryGetValue(key, out object? objectValue))
        {
            value = default;
            return false;
        }

        value = objectValue is null ? default : (T?)objectValue;
        return true;
    }
}

As mentioned in the comments above we really need to look at how much memory we’re actually saving with the current implementation. If we have 1000 live controls and are saving 32 bytes, that would be 32K of memory. 10,000 we’d save 320K. If we’re not saving several megabytes for 1000 controls, I don’t know that this complexity is worth maintaining. I’d rather see us gain the speed and simplicity and spend our time finding other ways to better use memory.

1reaction
elachlancommented, Jul 17, 2023

@merriemcgaw can you please tag this to .NET9? Also I don’t think it is an API suggestion because PropertyStore is Internal? Do we need a full review or just a team sign off on the plan?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Disposing of an MDI parent form twice throws an exception
Dispose() twice inside a form that has a child MDI form crashes the application with NullReferenceException . Inspecting platform code code ...
Read more >
PSCreateMemoryPropertyStore function (propsys.h)
This function creates an in-memory property store object that implements IPropertyStore, INamedPropertyStore, IPropertyStoreCache, ...
Read more >
Changelog — Scala IDE 0.1-SNAPSHOT documentation
... Refactor/remove todos; Set UTF-8 as default charset in test workspaces; Enable compiler ... Replaces PropertyStore with a set of sparser platform calls....
Read more >
Simple promise-based session middleware for Next.js, ...
next-session accepts the properties below. ... Refactor, remove rolling option and fix unreliable tests (#283); Avoid override set-cookie ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found