Come evitare il cast dell'oggetto dopo aver passato un parametro?

1

Ho un problema con l'implementazione dell'interazione generica dell'interfaccia utente.

Ho classi diverse che contengono dati ciascuno per particolari elementi dell'interfaccia. Quindi ogni UserInterfaceElementComponent ha dati utili solo per lui - tutti i dati ereditano da UserInterfaceElementComponentData . Per esempio, UserInterfaceElementRightCorner può utilizzare solo dati di tipo UserInterfaceElementComponentDataRightCorner .

Il problema è nella parte generica - Ho classe UserInterfaceManager che ha un metodo generico:

public static void TransferDataToUIController<T>(T userInterfaceElementComponentData) where T: UserInterfaceElementComponentData
{
    _userInterfaceController.TransferUIDataToComponent(userInterfaceElementComponentData);
}

Quindi da qualsiasi classe posso inviarlo tutti i dati che ereditano da UserInterfaceElementComponentData . E chiede al controllore di trasferire questi dati a un particolare UserInterfaceElementComponent ottenendo il suo tipo enum:

public void TransferUIDataToComponent<T>(T userInterfaceElementComponentData) where T: UserInterfaceElementComponentData
{
    this._userInterfaceElements[(int)userInterfaceElementComponentData.ElementComponentType].AcceptUserInterfaceElementComponentData(userInterfaceElementComponentData);
}

Quindi ottiene un particolare componente dell'interfaccia utente da questo array this._userInterfaceElements che corrisponde a questo tipo di dati. Quindi chiama per accettare i dati che gli sono stati dati.

public class UserInterfaceElementRightCorner : UserInterfaceElementComponent {

    public override void AcceptUserInterfaceElementComponentData(UserInterfaceElementComponentData userInterfaceElementComponentData)
    {
        ((UserInterfaceElementComponentDataRightCorner)userInterfaceElementComponentData)._planetInfoPanel.gameObject.SetActive(true);
    }
}

Sebbene sia necessario lanciare questo oggetto sul tipo corrispondente per ottenere questi dati particolari, quindi potrei usarlo in quella classe nel modo che mi serve, ma vorrei evitare il cast dell'oggetto perché verrà chiamato più di 10000 volte al secondo .

Non mi interessa cambiare l'intera architettura, ho solo bisogno di passare l'oggetto come parametro e dovrebbe già essere il tipo di cui ho bisogno. Anche se con i farmaci generici sembra impossibile.

Altrimenti, dovrò creare un nuovo metodo per ogni tipo in 2 classi e confrontare i tipi. In generale, ho bisogno di passare particolari tipi di dati a particolari client senza casting e con metodi 1-2.

    
posta Candid Moon 27.02.2017 - 23:05
fonte

5 risposte

1

La quarta volta è il fascino

Questa è la mia seconda risposta (quella che hai inizialmente controllato) e mi sono liberato della chiamata <OfType> e dell'enumeratore.

Il trucco qui è stato quello di definire un'interfaccia controvariante che accoppia i componenti e i loro dati. Avevo intenzione di saperne di più sulla contravarianza quindi è stato un esercizio divertente, grazie.

A questo punto potrebbe essere un po 'di codice confuso, ma penso di aver dimostrato che può essere fatto, e puoi usare la stessa tecnica ma forse ripulire un po' la struttura.

public enum ElementComponentTypeEnum { RightCorner = 1 }

public interface IRelated<in T1, in T2>  
{ 
    void AcceptUserInterfaceElementComponentData(T2 t2);
}
abstract public class UserInterfaceElementComponentData
{
    abstract public ElementComponentTypeEnum ElementComponentType { get; }
    abstract public void Apply(UserInterfaceController controller);
}

public class UserInterfaceElementComponentDataRightCorner : UserInterfaceElementComponentData
{
    public string TypeSpecificString
    {
        get
        {
            return "I am a UserInterfaceElementComponentDataRightCorner!!! I'm even hardcoded!!!";
        }
    }
    override public ElementComponentTypeEnum ElementComponentType { get { return ElementComponentTypeEnum.RightCorner; } }

    public override void Apply(UserInterfaceController controller)
    {
        IRelated<UserInterfaceElementRightCorner, UserInterfaceElementComponentDataRightCorner> component = controller.ResolveComponent<UserInterfaceElementRightCorner,UserInterfaceElementComponentDataRightCorner>(this.ElementComponentType);
        component.AcceptUserInterfaceElementComponentData(this);
    }
}


abstract public class UserInterfaceElementComponent : IRelated<UserInterfaceElementComponent, UserInterfaceElementComponentData>
{
    public void AcceptUserInterfaceElementComponentData(UserInterfaceElementComponentData data) { }
}


public class UserInterfaceElementRightCorner : UserInterfaceElementComponent, IRelated<UserInterfaceElementRightCorner,UserInterfaceElementComponentDataRightCorner>
{
    public void AcceptUserInterfaceElementComponentData(UserInterfaceElementComponentDataRightCorner data) 
    {
        Console.WriteLine("Hey I'm doing sonmething with a specific, uncast data class.  Here's proof: " + data.TypeSpecificString);
    }

}

public class UserInterfaceController
{
    private UserInterfaceElementComponent[] _userInterfaceElements = new UserInterfaceElementComponent[10];

    public UserInterfaceController()
    {
        _userInterfaceElements[(int)ElementComponentTypeEnum.RightCorner] = new UserInterfaceElementRightCorner();
    }
    public void TransferUIDataToComponent<T>(T userInterfaceElementComponentData) where T : UserInterfaceElementComponentData
    {
        userInterfaceElementComponentData.Apply(this);
    }

    public IRelated<T1,T2> ResolveComponent<T1,T2>(ElementComponentTypeEnum elementComponentType) where T1 : UserInterfaceElementComponent where T2: UserInterfaceElementComponentData
    {
        IRelated<T1,T2> result = _userInterfaceElements[(int)elementComponentType];
        return result;
    }
}
static public class CastlessDispatch
{
    static UserInterfaceController _userInterfaceController = new UserInterfaceController();
    public static void TransferDataToUIController<T>(T userInterfaceElementComponentData) where T : UserInterfaceElementComponentData
    {
        _userInterfaceController.TransferUIDataToComponent(userInterfaceElementComponentData);
    }
}
    
risposta data 01.03.2017 - 23:10
fonte
2

Penso che sia necessario un modo per legare un componente al tipo di dati che accetta. Altrimenti, come fai a sapere che corrispondono (a parte il fatto che analizzano il nome e usano la tua intuizione umana, cosa che un computer non può fare)?

Questo può essere fatto con generici ed ereditarietà, come questo:

abstract class MyComponentData
{
    abstract public string ElementComponentType { get; }
}
class MyComponentData1 : MyComponentData
{
    override public string ElementComponentType { get { return "Type1"; } }
}
class MyComponentData2 : MyComponentData
{
    override public string ElementComponentType { get { return "Type2"; } }
}

abstract class MyComponentClass
{
}
class MyComponentClass<T> : MyComponentClass where T: MyComponentData
{
    public void AcceptData(T input)
    {
        //Do something with the data
    }
}

Con questo schema, è necessario dichiarare quale tipo di dati accetterà un particolare componente come parte della dichiarazione della classe generica MyComponentClass. Quindi se hai un componente in grado di gestire MyComponentData1 , devi dichiararlo come var c = new MyComponentClass<MyComponentData1>() . Pertanto ogni istanza di un componente è associata a un tipo di dati specifico.

Per inizializzare l'elenco di ricerca:

Dictionary<string, MyComponentClass> _userInterfaceElements = new Dictionary<string, MyComponentClass>();
_userInterfaceElements.Add("Type1", new MyComponentClass<MyComponentData1>());
_userInterfaceElements.Add("Type2", new MyComponentClass<MyComponentData2>());

E per inviare una richiesta in arrivo per un componente, passando i dati, usando la tabella:

static void TransferUIDataToComponent<T>(T data) where T: MyComponentData
{
    MyComponentClass<T> c = _userInterfaceElements[data.ElementComponentType] as MyComponentClass<T>;
    c.AcceptData(data);
}

Ma ... hey .... non abbiamo più bisogno del tavolo! Quindi puoi fare questo:

static void TransferUIDataToComponent<T>(T data) where T: MyComponentData
{
    var c = new MyComponentClass<T>();
    c.AcceptData(data);
}

Significa che puoi liberarti di _userInterfaceElements , sbarazzarti di ElementComponentType e sbarazzarti dell'estratto MyComponentClass che ho aggiunto (ne avevamo solo bisogno per _userInterfaceElements in modo da poter memorizzare qualsiasi componente). E notate che abbiamo completamente rimosso la necessità di qualsiasi cast .

Quindi, per chiamare la spedizione:

var data1 = new MyComponentData1();
TransferUIDataToComponent<MyComponentData1>(data1);

var data2 = new MyComponentData2();
TransferUIDataToComponent<MyComponentData2>(data2);
    
risposta data 28.02.2017 - 03:46
fonte
1

Quello che @JohnWu delinea è il polimorfismo "regolare": implementare un'astrazione comune in tutti i tipi di implementazione per evitare il casting. Se puoi farlo, fallo.

Ma a seconda del tuo scenario, a volte questo non funziona. Quello che sembra chiedere nella tua domanda è il doppio invio: vuoi il tipo di entrambe le classi che implementano l'interfaccia e il tipo sottostante del parametro per controllare il binding di dispatch (così finisci un metodo con un parametro strongmente tipizzato).

Se questo è ciò che cerchi, non puoi averlo. Ma puoi imbrogliare: il pattern Vistor ti consente di implementare questo tipo di arrangiamenti usando il dispiegamento polimorfico "regolare", basato sul fatto che finisci per fare una singola spedizione due volte. Passa il target all'interfaccia del parametro e il parametro passa se stesso, strongmente digitato, alla destinazione.

Eric Lippert ha scritto un post molto interessante su questo blog che ha molto più senso della spiegazione sopra: link

    
risposta data 28.02.2017 - 05:58
fonte
1

Wow amico, hai odiato la mia altra soluzione. Indovina che non ho capito bene i tuoi NFR.

Ecco un'altra soluzione che mantiene la lista (quindi nessuna micro-istanza richiesta) e non ha ancora cast. Spero che tu sia felice.

L'idea qui è che rimuoviamo dalla classe base qualsiasi prototipo di metodo che deve essere specifico per tipo; dopo tutto, la classe base non ha idea di quale sia il suo tipo derivato. Invece implementiamo un comune metodo Apply che accetta il controller come argomento. Questo è un tipo di iniezione di dipendenza, ma a livello di metodo. Il controller funziona come una sorta di fabbrica, che può restituire componenti esistenti e strongmente tipizzati. Ogni classe di dati sa come trovare il suo componente partner utilizzando i generici.

Una volta ottenuto il componente, si tratta semplicemente di una chiamata al metodo AcceptUserInterfaceElementComponentData che ora è specifico per tipo e quindi può avere argomenti strongmente tipizzati.

Mi sono liberato dell'enum ... usare un enum per identificare il tipo mi sembrava un codice olfatto.

abstract public class UserInterfaceElementComponentData
{
    abstract public void Apply(UserInterfaceController controller);
}

public class UserInterfaceElementComponentDataRightCorner : UserInterfaceElementComponentData
{
    public string TypeSpecificExample
    {
        get
        {
            return "I am a UserInterfaceElementComponentDataRightCorner!!! I'm even hardcoded!!!";
        }
    }

    public override void Apply(UserInterfaceController controller)
    {
        var component = controller.ResolveComponent<UserInterfaceElementRightCorner>();
        component.AcceptUserInterfaceElementComponentData(this);
    }
}


abstract public class UserInterfaceElementComponent
{
}


public class UserInterfaceElementRightCorner : UserInterfaceElementComponent
{
    public void AcceptUserInterfaceElementComponentData(UserInterfaceElementComponentDataRightCorner userInterfaceElementComponentData)
    {
        Console.WriteLine("Hey I'm doing sonmething with a specific, uncast data class.  Here's proof: " + userInterfaceElementComponentData.TypeSpecificExample);
    }
}

public class UserInterfaceController
{
    List<UserInterfaceElementComponent> _userInterfaceElements = new List<UserInterfaceElementComponent>();

    public UserInterfaceController()
    {
        _userInterfaceElements.Add( new UserInterfaceElementRightCorner());
    }
    public void TransferUIDataToComponent<T>(T userInterfaceElementComponentData) where T : UserInterfaceElementComponentData
    {
        userInterfaceElementComponentData.Apply(this);
    }

    public T ResolveComponent<T>()
    {
        var result =  _userInterfaceElements.OfType<T>().FirstOrDefault();
        if (result == null) throw new InvalidOperationException(string.Format("Component {0} not found", typeof(T).FullName));
        return result;
    }
}
static class CastlessDispatch
{
    static UserInterfaceController _userInterfaceController = new UserInterfaceController();
    public static void TransferDataToUIController<T>(T userInterfaceElementComponentData) where T : UserInterfaceElementComponentData
    {
        _userInterfaceController.TransferUIDataToComponent(userInterfaceElementComponentData);
    }
}
    
risposta data 28.02.2017 - 21:06
fonte
1

Una delle limitazioni del linguaggio c # è che esiste un solo identificatore di tipo per un array o un elenco, quindi ogni elemento deve essere dello stesso tipo o derivare dallo stesso tipo. Se è derivato, l'unico modo per ottenere il sottotipo dall'array è di lanciarlo. Questo è vero, non importa quanto sei intelligente con i generici.

Ecco una soluzione che può sembrare un po 'brutta, ma risolve il problema del cast allontanandosi dai generici e inizializzando puntatori sicuri del tipo che possono essere usati senza alcun casting.

Ho mantenuto la lista, per farti felice, ma non fa nulla nel mio esempio.

abstract public class UserInterfaceElementComponentData
{
    abstract public void Apply(UserInterfaceController controller);
}

public class UserInterfaceElementComponentDataRightCorner : UserInterfaceElementComponentData
{
    public string TypeSpecificExample
    {
        get
        {
            return "I am a UserInterfaceElementComponentDataRightCorner!!! I'm even hardcoded!!!";
        }
    }

    public override void Apply(UserInterfaceController controller)
    {
        var component = controller.UserInterfaceElementRightCorner;
        component.AcceptUserInterfaceElementComponentData(this);
    }
}


abstract public class UserInterfaceElementComponent
{
}


public class UserInterfaceElementRightCorner : UserInterfaceElementComponent
{
    public void AcceptUserInterfaceElementComponentData(UserInterfaceElementComponentDataRightCorner userInterfaceElementComponentData)
    {
        Console.WriteLine("Hey I'm doing something with a specific, uncast data class.  Here's proof: " + userInterfaceElementComponentData.TypeSpecificExample);
    }
}

public class UserInterfaceController
{
    List<UserInterfaceElementComponent> _userInterfaceElements = new List<UserInterfaceElementComponent>();
    private UserInterfaceElementRightCorner _userInterfaceElementRightCorner;

    public UserInterfaceController()
    {
        _userInterfaceElementRightCorner = new UserInterfaceElementRightCorner());
        _userInterfaceElements.Add( _userInterfaceElementRightCorner );
    }
    public UserInterfaceElementRightCorner UserInterfaceElementRightCorner
    {
        get { return _userInterfaceElementRightCorner; }
    }
    public void TransferUIDataToComponent(UserInterfaceElementComponentData userInterfaceElementComponentData)
    {
        userInterfaceElementComponentData.Apply(this);
    }
}
static class CastlessDispatch
{
    static UserInterfaceController _userInterfaceController = new UserInterfaceController();
    public static void TransferDataToUIController(UserInterfaceElementComponentData userInterfaceElementComponentData)
    {
        _userInterfaceController.TransferUIDataToComponent(userInterfaceElementComponentData);
    }
}
    
risposta data 01.03.2017 - 12:11
fonte