Tengo la siguiente clase abstracta:
public abstract partial class AsyncResult { }
Y otras 7 clases que heredan de él (aquí hay solo 2 como referencia):
public partial class AsyncSearchResult : AsyncResult { private SearchResult searchResultField; /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Namespace="urn:core_2020_2.platform.webservices.netsuite.com", Order=0)] public SearchResult searchResult { get { return this.searchResultField; } set { this.searchResultField = value; } } } public partial class AsyncGetListResult : AsyncResult { private ReadResponseList readResponseListField; /// <remarks/> [System.Xml.Serialization.XmlElementAttribute(Order=0)] public ReadResponseList readResponseList { get { return this.readResponseListField; } set { this.readResponseListField = value; } } }
Tengo un método que devuelve el tipo abstracto y necesito convertirlo en uno de los tipos específicos para acceder a la propiedad deseada.
No puedo encontrar una manera de emitirlo en tiempo de compilación sin escribir 7 ifs para cada caso y duplicar mucho el código.
Agradezco cualquier tipo de ayuda.
Me gusta usar el patrón de visitante para esto:
public interface IAsyncResultVisitor { void Accept(AsyncSearchResult searchResult); void Accept(AsyncGetListResult getListResult); // Etc... } public abstract partial class AsyncResult { public abstract void Visit(IAsyncResultVisitor visitor); } public partial class AsyncSearchResult : AsyncResult { // ... public override void Visit(IAsyncResultVisitor visitor) => visitor.Accept(this); }
Después:
public class AsyncResultProcessor : IAsyncResultVisitor { public void ProcessAsyncResult(AsyncResult asyncResult) { asyncResult.Visit(this); } public void Accept(AsyncSearchResult searchResult) { // Access strongly-typed members of searchResult } public void Accept(AsyncGetListResult getListResult) { // Access strongly-typed members of getListResult } }
Tenga en cuenta que las declaraciones de cambio y las expresiones de cambio también ayudan aquí, aunque no garantizan que haya cubierto todos los casos (como lo hace el patrón de visitante):
switch (asyncResult) { case AsyncSearchResult searchResult: // ... break; }
Hubiera implementado el visitante de otra manera. El problema con la implementación de @ canton7 es que los datos tienen que saber cómo las personas deberían trabajar con ellos, ¡pero son solo datos simples!
Primero: AsyncResult
debe tener un constructor private protected
, por lo que ningún otro ensamblaje podría "implementar" esta clase abstracta
public abstract class AsyncResult { private protected AsyncResult() {} } public sealed class ResultOne : AsyncResult() { public string Data { get; set; } } // 6 more to go
Hacer interfaz para que coincida con todos los tipos
public interface IAsyncResultVisitor { void Visit(AsyncResult result); void VisitResultOne(ResultOne result); void VisitResultTwo(ResultTwo result); // 5 more to go }
Y cree un visitante predeterminado que tendrá propiedades para cada tipo que contenga una estrategia de visita
public sealed class LambdaAsyncResultVisitor : IAsyncResultVisitor { public Action<ResultOne> OnVisitOne { get; set; } public void VisitResultOne(ResultOne result) { if (OnVisitOne is null) throw new InvalidOperationException("..."); OnVisitOne.Invoke(result); } // 6 more to go }
Esto nos permitiría usarlo de esta manera.
AsyncResult result = ...; string someString = ""; new LambdaAsyncResultVisitor { OnVisitOne = r => someString = r.Data, OnVisitTwo = r => someString = "two", OnVisitThree => _ => throw new Exception("I know that is is impossible"), }.Visit(result); HandleSomeString(someString);
Esto brinda mucha más flexibilidad que el patrón de visitantes predeterminado.