Когда использование структур C # (типов значений) жертвует производительностью? - PullRequest
14 голосов
/ 09 декабря 2010

Я играл со структурами как с механизмом неявной проверки объектов со сложными значениями, а также с общими структурами вокруг более сложных классов для обеспечения допустимых значений. Я немного не осведомлен о последствиях производительности, так что я надеюсь, что вы все можете мне помочь. Например, если бы я делал что-то вроде инъекции объекта домена в оболочку типа значения, это вызывало бы проблемы? Зачем? Я понимаю разницу между типами значений и ссылочными типами, и моя цель здесь состоит в том, чтобы использовать различное поведение типов значений. Что именно мне нужно изучить, чтобы сделать это ответственно?

Вот чрезвычайно базовая идея того, о чем я думал.

public struct NeverNull<T>
    where T: class, new()
{

    private NeverNull(T reference)
    {
        _reference = reference;
    }

    private T _reference;

    public T Reference
    {
        get
        {
            if(_reference == null)
            {
                _reference = new T();
            }
            return _reference;
        }
        set
        {
            _reference = value;
        }
    }

    public static implicit operator NeverNull<T>(T reference)
    {
        return new NeverNull<T>(reference);
    }

    public static implicit operator T(NeverNull<T> value)
    {
        return value.Reference;
    }
}

Ответы [ 6 ]

11 голосов
/ 09 декабря 2010

Что ж, одна неприятная вещь заключается в том, что это не ведет себя так, как можно было бы ожидать наивно:

NeverNull<Foo> wrapper1 = new NeverNull<Foo>();
NeverNull<Foo> wrapper2 = wrapper1;

Foo foo1 = wrapper1;
Foo foo2 = wrapper2;

Это создаст два экземпляра Foo, потому чтоисходная версия была скопирована до того, как wrapper1 создал экземпляр.

По сути, вы имеете дело с изменяемой структурой - которая почти никогда не очень полезна.Кроме того, я обычно не заинтересован в неявных преобразованиях.

Такое ощущение, что вы пытаетесь создать магический код здесь ... и я вообще против такого рода вещей. Может быть, это имеет смысл для вашего конкретного случая использования, но я не могу придумать, где бы я лично хотел его использовать.

7 голосов
/ 09 декабря 2010

Как правильно указывает Джон, проблема здесь в том, что поведение типа неожиданно , а не медленно . С точки зрения производительности, накладные расходы на оболочку struct вокруг ссылки должны быть очень низкими.

Если то, что вы хотите сделать, это представить ненулевой ссылочный тип, то структура - разумный способ сделать это; тем не менее, я был бы склонен сделать структуру неизменной, потеряв функцию «автоматического создания»:

public struct NeverNull<T> where T: class 
{ 
    private NeverNull(T reference) : this()
    { 
        if (reference == null) throw new Exception(); // Choose the right exception
        this.Reference = reference; 
    } 

    public T Reference { get; private set; }

    public static implicit operator NeverNull<T>(T reference) 
    { 
        return new NeverNull<T>(reference); 
    } 

    public static implicit operator T(NeverNull<T> value) 
    { 
        return value.Reference; 
    } 
}

возложить на вызывающего абонента ответственность за предоставление действительной ссылки; если они хотят «новый», давайте.

Обратите внимание, что общие операторы преобразования могут дать вам неожиданные результаты. Вы должны прочитать спецификацию по операторам преобразования и полностью ее понять. Например, вы не можете создать ненулевую обертку вокруг «объекта» и затем неявно конвертировать эту вещь в конвертирование с развёртыванием; каждое неявное преобразование в объект будет преобразованием бокса в структуре. Вы не можете «заменить» встроенное преобразование языка C #.

2 голосов
/ 10 декабря 2010

Ответы на этот вопрос, похоже, отошли от обсуждения производительности и вместо этого обращаются к опасностям изменяемых типов значений.

На всякий случай, если вы сочтете это полезным, вот реализация, которую я собрал вместе, чтоделает что-то похожее на ваш исходный пример, используя обертку с неизменяемым типом значения.

Разница в том, что мой тип значения напрямую не ссылается на объект, к которому он относится;вместо этого он содержит ключ и ссылки на делегатов, которые выполняют поиск с использованием ключа (TryGetValueFunc) или создание с использованием ключа.(Примечание: моя оригинальная реализация имела оболочку, содержащую ссылку на объект IDictionary, но я изменил ее на делегат TryGetValueFunc, просто чтобы сделать его немного более гибким, хотя это может быть более запутанным, и я не уверен на 100%, чтоэто не открыло какой-то недостаток).

Обратите внимание, однако, что это может привести к непредвиденному поведению (в зависимости от того, что вы ожидаете), если вы манипулируете базовыми структурами данных, которые использует оболочка.accessances.

Ниже приведен полный рабочий пример вместе с примером использования консольной программы:

public delegate bool TryGetValueFunc<TKey, TValue>(TKey key, out TValue value);

public struct KeyedValueWrapper<TKey, TValue>
{
    private bool _KeyHasBeenSet;
    private TKey _Key;
    private TryGetValueFunc<TKey, TValue> _TryGetValue;
    private Func<TKey, TValue> _CreateValue;

    #region Constructors

    public KeyedValueWrapper(TKey key)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _TryGetValue = null;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TKey key, TryGetValueFunc<TKey, TValue> tryGetValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _TryGetValue = tryGetValue;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TKey key, Func<TKey, TValue> createValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _TryGetValue = null;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(TKey key, TryGetValueFunc<TKey, TValue> tryGetValue, Func<TKey, TValue> createValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _TryGetValue = tryGetValue;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(TryGetValueFunc<TKey, TValue> tryGetValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _TryGetValue = tryGetValue;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TryGetValueFunc<TKey, TValue> tryGetValue, Func<TKey, TValue> createValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _TryGetValue = tryGetValue;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(Func<TKey, TValue> createValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _TryGetValue = null;
        _CreateValue = createValue;
    }

    #endregion

    #region "Change" methods

    public KeyedValueWrapper<TKey, TValue> Change(TKey key)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, _TryGetValue, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, TryGetValueFunc<TKey, TValue> tryGetValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, tryGetValue, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, _TryGetValue, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, TryGetValueFunc<TKey, TValue> tryGetValue, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, tryGetValue, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TryGetValueFunc<TKey, TValue> tryGetValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, tryGetValue, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TryGetValueFunc<TKey, TValue> tryGetValue, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, tryGetValue, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, _TryGetValue, createValue);
    }

    #endregion

    public TValue Value
    {
        get
        {
            if (!_KeyHasBeenSet)
                throw new InvalidOperationException("A key must be specified.");

            if (_TryGetValue == null)
                throw new InvalidOperationException("A \"try get value\" delegate must be specified.");

            // try to find a value in the given dictionary using the given key
            TValue value;
            if (!_TryGetValue(_Key, out value))
            {
                if (_CreateValue == null)
                    throw new InvalidOperationException("A \"create value\" delegate must be specified.");

                // if not found, create a value
                value = _CreateValue(_Key);
            }
            // then return that value
            return value;
        }
    }
}

class Foo
{
    public string ID { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var dictionary = new Dictionary<string, Foo>();

        Func<string, Foo> createValue = (key) =>
        {
            var foo = new Foo { ID = key };
            dictionary.Add(key, foo);
            return foo;
        };

        // this wrapper object is not useable, since no key has been specified for it yet
        var wrapper = new KeyedValueWrapper<string, Foo>(dictionary.TryGetValue, createValue);

        // create wrapper1 based on the wrapper object but changing the key to "ABC"
        var wrapper1 = wrapper.Change("ABC");
        var wrapper2 = wrapper1;

        Foo foo1 = wrapper1.Value;
        Foo foo2 = wrapper2.Value;

        Console.WriteLine("foo1 and foo2 are equal? {0}", object.ReferenceEquals(foo1, foo2));
        // Output: foo1 and foo2 are equal? True

        // create wrapper1 based on the wrapper object but changing the key to "BCD"
        var wrapper3 = wrapper.Change("BCD");
        var wrapper4 = wrapper3;

        Foo foo3 = wrapper3.Value;
        dictionary = new Dictionary<string, Foo>(); // throw a curve ball by reassigning the dictionary variable
        Foo foo4 = wrapper4.Value;

        Console.WriteLine("foo3 and foo4 are equal? {0}", object.ReferenceEquals(foo3, foo4));
        // Output: foo3 and foo4 are equal? True

        Console.WriteLine("foo1 and foo3 are equal? {0}", object.ReferenceEquals(foo1, foo3));
        // Output: foo1 and foo3 are equal? False
    }
}

Альтернативная реализация с использованием IDictionary<string, Foo> вместо TryGetValueFunc<string, Foo>.Обратите внимание на контрпример, который я вставил в код использования:

public struct KeyedValueWrapper<TKey, TValue>
{
    private bool _KeyHasBeenSet;
    private TKey _Key;
    private IDictionary<TKey, TValue> _Dictionary;
    private Func<TKey, TValue> _CreateValue;

    #region Constructors

    public KeyedValueWrapper(TKey key)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _Dictionary = null;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TKey key, IDictionary<TKey, TValue> dictionary)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _Dictionary = dictionary;
        _CreateValue = null;
    }

    public KeyedValueWrapper(TKey key, Func<TKey, TValue> createValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _Dictionary = null;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(TKey key, IDictionary<TKey, TValue> dictionary, Func<TKey, TValue> createValue)
    {
        _Key = key;
        _KeyHasBeenSet = true;
        _Dictionary = dictionary;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(IDictionary<TKey, TValue> dictionary)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _Dictionary = dictionary;
        _CreateValue = null;
    }

    public KeyedValueWrapper(IDictionary<TKey, TValue> dictionary, Func<TKey, TValue> createValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _Dictionary = dictionary;
        _CreateValue = createValue;
    }

    public KeyedValueWrapper(Func<TKey, TValue> createValue)
    {
        _Key = default(TKey);
        _KeyHasBeenSet = false;
        _Dictionary = null;
        _CreateValue = createValue;
    }

    #endregion

    #region "Change" methods

    public KeyedValueWrapper<TKey, TValue> Change(TKey key)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, _Dictionary, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, IDictionary<TKey, TValue> dictionary)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, dictionary, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, _Dictionary, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(TKey key, IDictionary<TKey, TValue> dictionary, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(key, dictionary, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(IDictionary<TKey, TValue> dictionary)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, dictionary, _CreateValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(IDictionary<TKey, TValue> dictionary, Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, dictionary, createValue);
    }

    public KeyedValueWrapper<TKey, TValue> Change(Func<TKey, TValue> createValue)
    {
        return new KeyedValueWrapper<TKey, TValue>(_Key, _Dictionary, createValue);
    }

    #endregion

    public TValue Value
    {
        get
        {
            if (!_KeyHasBeenSet)
                throw new InvalidOperationException("A key must be specified.");

            if (_Dictionary == null)
                throw new InvalidOperationException("A dictionary must be specified.");

            // try to find a value in the given dictionary using the given key
            TValue value;
            if (!_Dictionary.TryGetValue(_Key, out value))
            {
                if (_CreateValue == null)
                    throw new InvalidOperationException("A \"create value\" delegate must be specified.");

                // if not found, create a value and add it to the dictionary
                value = _CreateValue(_Key);
                _Dictionary.Add(_Key, value);
            }
            // then return that value
            return value;
        }
    }
}

class Foo
{
    public string ID { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        // this wrapper object is not useable, since no key has been specified for it yet
        var wrapper = new KeyedValueWrapper<string, Foo>(new Dictionary<string, Foo>(), (key) => new Foo { ID = key });

        // create wrapper1 based on the wrapper object but changing the key to "ABC"
        var wrapper1 = wrapper.Change("ABC");
        var wrapper2 = wrapper1;

        Foo foo1 = wrapper1.Value;
        Foo foo2 = wrapper2.Value;

        Console.WriteLine("foo1 and foo2 are equal? {0}", object.ReferenceEquals(foo1, foo2));
        // Output: foo1 and foo2 are equal? True

        // create wrapper1 based on the wrapper object but changing the key to "BCD"
        var wrapper3 = wrapper.Change("BCD");
        var wrapper4 = wrapper3;

        Foo foo3 = wrapper3.Value;
        Foo foo4 = wrapper4.Value;

        Console.WriteLine("foo3 and foo4 are equal? {0}", object.ReferenceEquals(foo3, foo4));
        // Output: foo3 and foo4 are equal? True

        Console.WriteLine("foo1 and foo3 are equal? {0}", object.ReferenceEquals(foo1, foo3));
        // Output: foo1 and foo3 are equal? False


        // Counter-example: manipulating the dictionary instance that was provided to the wrapper can disrupt expected behavior
        var dictionary = new Dictionary<string, Foo>();

        var wrapper5 = wrapper.Change("CDE", dictionary);
        var wrapper6 = wrapper5;

        Foo foo5 = wrapper5.Value;
        dictionary.Clear();
        Foo foo6 = wrapper6.Value;

        // one might expect this to be true:
        Console.WriteLine("foo5 and foo6 are equal? {0}", object.ReferenceEquals(foo5, foo6));
        // Output: foo5 and foo6 are equal? False
    }
}
2 голосов
/ 09 декабря 2010

Хорошо, только примечание к вышесказанному.

MyStruct st; foo.Bar (ул); // st скопирован

Это не бокс, если параметр Bar не является объектом, например.

void Bar(MyStruct parameter){}

не будет указывать тип значения.

Параметры передаются по значению в c # по умолчанию, если вы не используете ключевое слово ref или out. Параметры, переданные по значению, копируются. Разница между передачей структуры и объекта - это то, что передается. С типом значения, фактическое значение копируется в, что означает, что создается новый тип значения, так что вы получите копию. Со ссылочным типом передается ссылка на ссылочный тип. Подсказки в названии, которое я предполагаю:)

Таким образом, производительность ухудшается, потому что вся структура копируется, если только вы не используете ключевое слово ref / out, и если вы делаете это интенсивно, я думаю, что ваш код требует рассмотрения.

Бокс - это процесс присвоения типа значения переменной ссылочного типа. Создается новый ссылочный тип (объект) и ему присваивается копия типа значения.

Я вроде понял, что вы делали в исходном коде, но похоже, что он решает одну простую проблему, которая имеет много явных, а не явных сложностей.

2 голосов
/ 09 декабря 2010

Основной штраф с боксом для структур. Также они передаются по значению, поэтому большая структура при передаче методу должна быть скопирована :

MyStruct st;
foo.Bar(st); // st is copied
1 голос
/ 09 декабря 2010

Другая проблема с производительностью возникает, когда вы помещаете структуры в коллекции.Например, представьте, что у вас есть List<SomeStruct>, и вы хотите изменить свойство Prop1 первого элемента в списке.Начальная склонность состоит в том, чтобы написать это:

List<SomeStruct> MyList = CreateList();
MyList[0].Prop1 = 42;

Это не собирается компилировать.Чтобы сделать эту работу, вы должны написать:

SomeStruct myThing = MyList[0];
myThing.Prop1 = 42;
MyList[0] = myThing.Prop1;

Это вызывает две проблемы (в первую очередь).Сначала вы копируете всю структуру дважды: один раз в рабочий экземпляр myThing, а затем обратно в список.Вторая проблема заключается в том, что вы не можете сделать это в foreach, потому что это изменяет коллекцию и заставляет перечислитель выдавать исключение.

Кстати, ваша вещь NeverNull имеет довольно странныйповедение.Можно установить для свойства Reference значение null.Мне кажется очень и очень странным, что это утверждение:

var Contradiction = new NeverNull<object>(null);

действительно.

Мне было бы интересно узнать причины, по которым вы пытаетесь создать этот тип структуры.

...