Reputation: 13209
UPDATE
Requesting to re-open because the other SO answers don't have a solution, but one of the comments to the question has a solution I want to accept as it works for the scenario.
Original Question
I am having trouble writing extension methods with non-abstract base classes and sub-classes that select the appropriate extension method.
I have a very simple example below (abstracted from a much bigger project) that uses the extension method "Run". The expected output is listed in the comment next to each class.
public class Parent { }; // Should output "Parent"
public class ChildA : Parent { }; // Should output "Child A"
public class ChildB : Parent { }; // Should output "Parent"
// Expected Output: ChildA, Parent, Parent
public class Program
{
public static void Main()
{
var commands = new List<Parent>() { new ChildA(), new ChildB(), new Parent() };
Console.WriteLine(string.Join(", ", commands.Select(c => c.Run())));
}
}
Here are my attempts so far, but there has to be a cleaner way to do this:
List of attempts
public static class Extensions
{
public static string Run(this ChildA model)
{
return "ChildA";
}
public static string Run(this Parent model)
{
return model.Run1(); // Change to test different approaches
}
public static string Run1(this Parent model) // No type-checking
{
return "Parent";
}
public static string Run2(this Parent model) // Explicitly check sub-types
{
if (model is ChildA)
return ((ChildA)model).Run();
else
return "Parent";
}
public static string Run3(this Parent model) // Attempted dynamic type conversion
{
if (model.GetType().BaseType == typeof(Parent))
{
dynamic changedObj = Convert.ChangeType(model, model.GetType());
return changedObj.Run();
}
else
return "Parent";
}
public static string Run4(this Parent model) // Attempted reflected generic type conversion
{
if (model.GetType().BaseType == typeof(Parent))
{
var method = typeof(Extensions).GetMethod("Cast");
var generic = method.MakeGenericMethod(new[] { model.GetType() });
//var generic = generic.Invoke(new object(), null);
//return generic.Run();
return "Not working yet";
}
else
return "Parent";
}
public static T Cast<T>(this object input)
{
return (T) input;
}
}
Upvotes: 2
Views: 2718
Reputation: 35730
the best Run method overload is resolved at compile-time and for items of List<Parent>
it is Run(this Parent model)
. Polymorphic behavior can be imitated using reflection in extension method
using System;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
public static class Extensions
{
private static Dictionary<Type, MethodInfo> _runs;
private static Type _parentType;
static Extensions()
{
_parentType = typeof(Parent);
_runs = new Dictionary<Type, MethodInfo>();
// overloads of Run method, which return string for different types derived from Parent
var methods = typeof(Extensions)
.GetMethods(BindingFlags.Static|BindingFlags.Public)
.Where(m => m.Name == "Run" && m.ReturnType == typeof(string));
foreach(var mi in methods)
{
var args = mi.GetParameters();
// method should have only one parameter
if (args.Length != 1 || _parentType.IsAssignableFrom(args[0].ParameterType) == false)
return;
_runs.Add(args[0].ParameterType, mi);
}
}
// overloads
public static string Run(this ChildA model)
{
return "ChildA";
}
public static string Run(this Parent model, object args)
{
// this method is not added to _runs (2 parameters)
return null;
}
public static int Run(this ChildC model)
{
// this method is not added to _runs (return int)
return 0;
}
public static string Run(this Parent model) // Attempted dynamic type conversion
{
// not really correct
if (model == null)
return "Parent";
var t = model.GetType();
if (t == _parentType)
return "Parent";
// invoke overload for type t
if (_runs.ContainsKey(t))
return (string) _runs[t].Invoke(null, new object[] {model});
return "Not working yet";
}
}
// usage
public class Parent { }; // Should output "Parent"
public class ChildA : Parent { }; // Should output "Child A"
public class ChildB : Parent { }; // Should output "Not working yet"
public class ChildC : Parent { };
public class Program
{
public static void Main()
{
var commands = new List<Parent>() { new ChildA(), new ChildB(), new Parent(), new ChildC(), (ChildA)null};
Console.WriteLine(string.Join(", ", commands.Select(c => c.Run())));
// extension method can be invoked for null
Console.WriteLine(((ChildA)null).Run());
//// crashes on (ChildA)null with error:
//// The call is ambiguous between the following methods or properties: 'Extensions.Run(ChildA)' and 'Extensions.Run(ChildC)'
//Console.WriteLine(string.Join(", ", commands.Select(c => Extensions.Run(c as dynamic))));
}
}
resume
extension method can be invoked as common method (.Run()
), not as static Extensions.Run
extension method Run(this Parent model)
has problems with null
argument (cannot resolve type correct)
trick with dynamic
works in most situations, but :
int Run(this ChildC model)
method, which returns int
not string
like others (when (ChildA)null
is removed from list)The call is ambiguous between the following methods or properties: 'Extensions.Run(ChildA)' and 'Extensions.Run(ChildC)'
error (i don't understand that)Upvotes: 1
Reputation: 3752
Creating the two extension methods for Parent
and ChildA
, you can move the association to runtime by using dynamic
.
Console.WriteLine(string.Join(", ", commands.Select(c => Extensions.Run(c as dynamic))));
Upvotes: 2