Pregunta

He estado jugando con estructuras como un mecanismo para validar implícitamente objetos de valor complejas, así como estructuras genéricas en torno a las clases más complejas para asegurar los valores válidos. Estoy un poco ignorantes en cuanto a las consecuencias de rendimiento, así que espero que todos ustedes me puede ayudar a cabo. Por ejemplo, si tuviera que hacer algo como la inyección de un objeto de dominio en un tipo de envoltorio valor, sería que causan problemas? ¿Por qué? Entiendo la diferencia entre los tipos de valor y tipos de referencia, y mi objetivo aquí es aprovechar el diferente comportamiento de los tipos de valor. ¿Qué es exactamente lo que necesito para estudiar con el fin de hacer esto de forma responsable?

Esta es una idea muy básica de algo que estaba pensando.

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;
    }
}
¿Fue útil?

Solución

Bueno, una cosa desagradable es que esto no se comporta como se podría esperar que ingenuamente:

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

Foo foo1 = wrapper1;
Foo foo2 = wrapper2;

Esto va a crear dos casos de Foo porque la versión original fue copiado, antes wrapper1 creado una instancia.

Básicamente, se trata con una estructura mutable - que es casi no una buena cosa que tiene. Además, no estoy interesado en general, las conversiones implícitas.

Se siente como que está tratando de lograr código mágico de aspecto aquí ... y estoy generalmente en contra de ese tipo de cosas. Tal vez tiene sentido para su caso en particular, pero no puedo pensar en dónde estaría personalmente quiero usarlo.

Otros consejos

Como Jon señala correctamente, el problema aquí es que el comportamiento del tipo es inesperada , no es que sea lenta . Desde una perspectiva de rendimiento, la sobrecarga de la envoltura struct alrededor de la referencia debe ser muy bajo.

Si lo que quieres hacer es representar un tipo de referencia no anulable a continuación, una estructura es una manera razonable de hacerlo; Sin embargo, me inclinaría a hacer que la estructura inmutable por la pérdida de la función de "creación automática":

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; 
    } 
}

Hacer la persona que llama responsable de proporcionar una referencia válida; si quieren "nueva" uno, dejar que ellos.

Tenga en cuenta también que los operadores de conversión genéricos puede producir resultados inesperados. Debe leer la especificación de los operadores de conversión y entender a fondo. Por ejemplo, no se puede hacer una envoltura no nulo en torno a "objeto" y luego tener esa cosa implícitamente convertir a la conversión desenvolver; cada conversión implícita al objeto será una conversión de boxeo en la estructura. No se puede "sustituir" un sistema incorporado en la conversión del lenguaje C #.

La pena principal es con boxeo para estructuras. También se pasa por valor lo que una gran estructura, cuando pasó a un método tendrá que ser copiados

MyStruct st;
foo.Bar(st); // st is copied

Ok, sólo una nota en lo anterior.

MyStruct st; foo.Bar (st); // st se copia

Esto no es el boxeo, a menos que el parámetro de Bar es objeto, por ejemplo.

void Bar(MyStruct parameter){}

No sería la caja el tipo de valor.

Los parámetros se pasan por valor en C # por defecto, a menos que utilice el árbitro o fuera de palabras clave. Los parámetros pasados ??por valor se copian. La diferencia entre pasar la estructura y el objeto es lo que se transmite. Con un tipo de valor, se crea el valor real se copia en, lo que significa un nuevo tipo de valor, por lo que terminan con una copia. Con una referencia escriba la referencia al tipo de referencia se pasa en pistas en el nombre supongo:.)

Así que hay un impacto en el rendimiento de estructuras porque toda la estructura se copia a menos que utilice el árbitro de entrada / salida de palabras clave, y si usted está haciendo eso mucho, creo que sus necesidades de código de mirar.

El boxeo es el proceso de asignar un tipo de valor a una variable de tipo de referencia. Un nuevo tipo de referencia se crea (objeto) y una copia del tipo valor asignado en ella.

En cierto modo me consiguió lo que estaba haciendo en el código original, pero que parece ser la solución de un problema sencillo con uno que tiene muchos más implícito que explícito complejidades.

Las respuestas en esta cuestión parecen haberse alejado de discutir el rendimiento y en su lugar están frente a los peligros de los tipos de valor mutables.

En caso de que encuentre este artículo útil, aquí es una aplicación que me tiró juntos que hace algo similar a su original ejemplo utilizando un envoltorio tipo de valor inmutable.

La diferencia es que mi tipo de valor no hace referencia directamente el objeto al que se refiere; en cambio, tiene una llave y las referencias a los delegados que realizan una búsqueda, ya sea mediante la tecla (TryGetValueFunc) o una cree utilizando la tecla. (Nota: mi implementación original tenía la envoltura de la celebración de una referencia a un objeto IDictionary, pero lo cambió a un delegado TryGetValueFunc sólo para que sea un poco más flexible, aunque esto puede ser más confuso, y no estoy 100% seguro que al hacerlo, no se podía abrir algún tipo de defecto).

Tenga en cuenta, sin embargo, que esto podría todavía resultar en un comportamiento inesperado (dependiendo de lo que se espera), si está manipulando las estructuras de datos subyacentes que los accesos de envoltura.

A continuación se muestra un ejemplo de trabajo completo, junto con un ejemplo de uso de un programa de consola:

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
    }
}

Implementación alternativa usando IDictionary<string, Foo> en lugar de TryGetValueFunc<string, Foo>. Tenga en cuenta el contra-ejemplo que puse en el código de uso:

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
    }
}

Otro problema de rendimiento viene cuando se pone estructuras en las colecciones. Por ejemplo, imagine que tiene un List<SomeStruct>, y que desea modificar la propiedad Prop1 del primer elemento de la lista. La inclinación inicial es escribir esto:

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

Eso no va a compilar. Con el fin de hacer este trabajo que tiene que escribir:

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

Esto causa dos problemas (principalmente). En primer lugar, se termina la copia de toda la estructura dos veces: una vez en su instancia myThing de trabajo, y luego de vuelta a la lista. El segundo problema es que no se puede hacer esto en un foreach porque cambia la recogida y hará que el empadronador a lanzar una excepción.

Por cierto, su cosa NeverNull tiene un comportamiento bastante extraño. Es posible establecer la propiedad Reference a null. Me parece muy extraño que esta declaración:

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

Es válido.

Yo estaría interesado en saber las razones que tiene para tratar de crear este tipo de estructura.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top