I have the following abstract class:
public abstract partial class AsyncResult
{
}
And 7 other classes that inherit from it(here are only 2 for reference):
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;
}
}
}
I have a method that returns the abstract type and I need to cast it to one of the specific types in order to access the desired property.
I can't find a way to cast it at compile time without writing 7 ifs for each case and duplicate the code a lot.
Appreciate any kind of help.
I like using the visitor pattern for this:
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);
}
Then:
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
}
}
Note that switch statements and switch expressions also help here, although they don't ensure that you've covered every single case (as the visitor pattern does):
switch (asyncResult)
{
case AsyncSearchResult searchResult:
// ...
break;
}
I would've implemented visitor in different way. Problem with @canton7 implementation is that data have to know how people should work with it, but it's just plain data!
First: AsyncResult
should have private protected
constructor, so no other assemblies could "implement" this abstract class
public abstract class AsyncResult
{
private protected AsyncResult() {}
}
public sealed class ResultOne : AsyncResult()
{
public string Data { get; set; }
}
// 6 more to go
Make interface to match all types
public interface IAsyncResultVisitor
{
void Visit(AsyncResult result);
void VisitResultOne(ResultOne result);
void VisitResultTwo(ResultTwo result);
// 5 more to go
}
And create default visitor that's gonna have properties for each type containing visit strategy
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
}
This would allow us to use it this way
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);
This gives much more flexibility than default visitor pattern