Det her bliver en meget lang post.
:-)
Lad os foerst tage scenariet med en enkelt klasse. Efter min bedste overbevisning boer den haandkodes lige ud af landevejen uanset antallet af properties. Det er type safe. Det performer godt. Og det boer vaere til at vedligeholder.
Jeg ville kode det lidt anderledes end ovenfor. Jeg ville lave det "database style".
using System;
using System.Collections.Generic;
using System.Linq;
namespace E
{
public class Data
{
public string V1 { get; set; }
public string V2 { get; set; }
public override string ToString()
{
return string.Format("{{V1={0},V2={1}}}", V1, V2);
}
}
public class Data1
{
public Data A { get; set; }
public string B { get; set; }
public string C { get; set; }
public override string ToString()
{
return string.Format("{{A={0},B={1},C={2}}}", A, B, C);
}
}
public static class Util
{
public static List<Data1> Filter(this List<Data1> lst, string av1, string av2, string b, string c)
{
return lst.Where(o => (o.A.V1 == (av1 ?? o.A.V1)) &&
(o.A.V2 == (av2 ?? o.A.V2)) &&
(o.B == (b ?? o.B)) &&
(o.C == (c ?? o.C))).ToList();
}
}
public class Program
{
public static void Main(string[] args)
{
List<Data1> lst1 = new List<Data1> { new Data1 { A = new Data { V1 = "1AV1", V2 = "1AV2" }, B = "1B", C = "1C" },
new Data1 { A = new Data { V1 = "2AV1", V2 = "2AV2" }, B = "2B", C = "2C" },
new Data1 { A = new Data { V1 = "3AV1", V2 = "3AV2" }, B = "3B", C = "3C" } };
foreach(Data1 o in lst1)
{
Console.WriteLine(o);
}
foreach(Data1 o in lst1.Filter(null, "2AV2", null, null))
{
Console.WriteLine(o);
}
Console.ReadKey();
}
}
}
Saa er der problemet med mange klasser. Man kan naturligvis lave det paa samme maade som med en enkelt klasse. En haandkodet metode. Det virker men giver naturligvis en metode per data klasse.
using System;
using System.Collections.Generic;
using System.Linq;
namespace E
{
public class Data
{
public string V1 { get; set; }
public string V2 { get; set; }
public override string ToString()
{
return string.Format("{{V1={0},V2={1}}}", V1, V2);
}
}
public class Data1
{
public Data A { get; set; }
public string B { get; set; }
public string C { get; set; }
public override string ToString()
{
return string.Format("{{A={0},B={1},C={2}}}", A, B, C);
}
}
public class Data2
{
public Data D { get; set; }
public string E { get; set; }
public override string ToString()
{
return string.Format("{{D={0},E={1}}}", D, E);
}
}
public static class Util
{
public static List<Data1> Filter(this List<Data1> lst, string av1, string av2, string b, string c)
{
return lst.Where(o => (o.A.V1 == (av1 ?? o.A.V1)) &&
(o.A.V2 == (av2 ?? o.A.V2)) &&
(o.B == (b ?? o.B)) &&
(o.C == (c ?? o.C))).ToList();
}
public static List<Data2> Filter(this List<Data2> lst, string dv1, string dv2, string e)
{
return lst.Where(o => (o.D.V1 == (dv1 ?? o.D.V1)) &&
(o.D.V2 == (dv2 ?? o.D.V2)) &&
(o.E == (e ?? o.E))).ToList();
}
}
public class Program
{
public static void Main(string[] args)
{
List<Data1> lst1 = new List<Data1> { new Data1 { A = new Data { V1 = "1AV1", V2 = "1AV2" }, B = "1B", C = "1C" },
new Data1 { A = new Data { V1 = "2AV1", V2 = "2AV2" }, B = "2B", C = "2C" },
new Data1 { A = new Data { V1 = "3AV1", V2 = "3AV2" }, B = "3B", C = "3C" } };
List<Data2> lst2 = new List<Data2> { new Data2 { D = new Data { V1 = "4DV1", V2 = "4DV2" }, E = "4E" },
new Data2 { D = new Data { V1 = "5DV1", V2 = "5DV2" }, E = "5E" },
new Data2 { D = new Data { V1 = "6DV1", V2 = "6DV2" }, E = "6E" } };
foreach(Data1 o in lst1)
{
Console.WriteLine(o);
}
foreach(Data2 o in lst2)
{
Console.WriteLine(o);
}
foreach(Data1 o in lst1.Filter(null, "2AV2", null, null))
{
Console.WriteLine(o);
}
foreach(Data2 o in lst2.Filter("6DV1", null, "6E"))
{
Console.WriteLine(o);
}
Console.ReadKey();
}
}
}
Jeg foretraekker faktisk en anden variant af dette, hvor metoden flyttes ind i klassen og der er et interface til at sikre at den faktisk er der.
using System;
using System.Collections.Generic;
using System.Linq;
namespace E
{
public class Data
{
public string V1 { get; set; }
public string V2 { get; set; }
public override string ToString()
{
return string.Format("{{V1={0},V2={1}}}", V1, V2);
}
}
public class Data1 : IFiltrable
{
public Data A { get; set; }
public string B { get; set; }
public string C { get; set; }
public override string ToString()
{
return string.Format("{{A={0},B={1},C={2}}}", A, B, C);
}
public bool Accept(Dictionary<string, string> filter)
{
return (A.V1 == (filter.Get("A.V1") ?? A.V1)) &&
(A.V2 == (filter.Get("A.V2") ?? A.V2)) &&
(B == (filter.Get("B") ?? B)) &&
(C == (filter.Get("C") ?? C));
}
}
public class Data2 : IFiltrable
{
public Data D { get; set; }
public string E { get; set; }
public override string ToString()
{
return string.Format("{{D={0},E={1}}}", D, E);
}
public bool Accept(Dictionary<string, string> filter)
{
return (D.V1 == (filter.Get("D.V1") ?? D.V1)) &&
(D.V2 == (filter.Get("D.V2") ?? D.V2)) &&
(E == (filter.Get("E") ?? E));
}
}
public interface IFiltrable
{
bool Accept(Dictionary<string, string> filter);
}
public static class Util
{
public static TV Get<TK, TV>(this Dictionary<TK, TV> d, TK key) where TV : class
{
if(d.ContainsKey(key))
{
return d[key];
}
else
{
return null;
}
}
public static List<T> Filter<T>(this List<T> lst, Dictionary<string, string> filtervals) where T : IFiltrable
{
return lst.Where(o => o.Accept(filtervals)).ToList();
}
}
public class Program
{
public static void Main(string[] args)
{
List<Data1> lst1 = new List<Data1> { new Data1 { A = new Data { V1 = "1AV1", V2 = "1AV2" }, B = "1B", C = "1C" },
new Data1 { A = new Data { V1 = "2AV1", V2 = "2AV2" }, B = "2B", C = "2C" },
new Data1 { A = new Data { V1 = "3AV1", V2 = "3AV2" }, B = "3B", C = "3C" } };
List<Data2> lst2 = new List<Data2> { new Data2 { D = new Data { V1 = "4DV1", V2 = "4DV2" }, E = "4E" },
new Data2 { D = new Data { V1 = "5DV1", V2 = "5DV2" }, E = "5E" },
new Data2 { D = new Data { V1 = "6DV1", V2 = "6DV2" }, E = "6E" } };
foreach(Data1 o in lst1)
{
Console.WriteLine(o);
}
foreach(Data2 o in lst2)
{
Console.WriteLine(o);
}
Dictionary<string, string> flt1 = new Dictionary<string, string>();
flt1.Add("A.V2", "2AV2");
foreach(Data1 o in lst1.Filter(flt1))
{
Console.WriteLine(o);
}
Dictionary<string, string> flt2 = new Dictionary<string, string>();
flt2.Add("A.V1", "6DV1");
flt2.Add("E", "6E");
foreach(Data2 o in lst2.Filter(flt2))
{
Console.WriteLine(o);
}
Console.ReadKey();
}
}
}
Ulempen ved ovenstaaende er at det nogen gange kan vaere et problem at kraeve noget (aendre noget) af data klassen.
Det problem man loeses ved at lave en ny filter klasse per data klasses (stadig med et interface).
using System;
using System.Collections.Generic;
using System.Linq;
namespace E
{
public class Data
{
public string V1 { get; set; }
public string V2 { get; set; }
public override string ToString()
{
return string.Format("{{V1={0},V2={1}}}", V1, V2);
}
}
public class Data1
{
public Data A { get; set; }
public string B { get; set; }
public string C { get; set; }
public override string ToString()
{
return string.Format("{{A={0},B={1},C={2}}}", A, B, C);
}
}
public class Data2
{
public Data D { get; set; }
public string E { get; set; }
public override string ToString()
{
return string.Format("{{D={0},E={1}}}", D, E);
}
}
public interface IFilter<T>
{
bool Accept(Dictionary<string, string> filter, T o);
}
public class Data1Filter : IFilter<Data1>
{
public bool Accept(Dictionary<string, string> filter, Data1 o)
{
return (o.A.V1 == (filter.Get("A.V1") ?? o.A.V1)) &&
(o.A.V2 == (filter.Get("A.V2") ?? o.A.V2)) &&
(o.B == (filter.Get("B") ?? o.B)) &&
(o.C == (filter.Get("C") ?? o.C));
}
}
public class Data2Filter : IFilter<Data2>
{
public bool Accept(Dictionary<string, string> filter, Data2 o)
{
return (o.D.V1 == (filter.Get("D.V1") ?? o.D.V1)) &&
(o.D.V2 == (filter.Get("D.V2") ?? o.D.V2)) &&
(o.E == (filter.Get("E") ?? o.E));
}
}
public static class Util
{
public static TV Get<TK, TV>(this Dictionary<TK, TV> d, TK key) where TV : class
{
if(d.ContainsKey(key))
{
return d[key];
}
else
{
return null;
}
}
public static List<T> Filter<T>(this List<T> lst, IFilter<T> filter, Dictionary<string, string> filtervals)
{
return lst.Where(o => filter.Accept(filtervals, o)).ToList();
}
}
public class Program
{
public static void Main(string[] args)
{
List<Data1> lst1 = new List<Data1> { new Data1 { A = new Data { V1 = "1AV1", V2 = "1AV2" }, B = "1B", C = "1C" },
new Data1 { A = new Data { V1 = "2AV1", V2 = "2AV2" }, B = "2B", C = "2C" },
new Data1 { A = new Data { V1 = "3AV1", V2 = "3AV2" }, B = "3B", C = "3C" } };
List<Data2> lst2 = new List<Data2> { new Data2 { D = new Data { V1 = "4DV1", V2 = "4DV2" }, E = "4E" },
new Data2 { D = new Data { V1 = "5DV1", V2 = "5DV2" }, E = "5E" },
new Data2 { D = new Data { V1 = "6DV1", V2 = "6DV2" }, E = "6E" } };
foreach(Data1 o in lst1)
{
Console.WriteLine(o);
}
foreach(Data2 o in lst2)
{
Console.WriteLine(o);
}
Dictionary<string, string> flt1 = new Dictionary<string, string>();
flt1.Add("A.V2", "2AV2");
foreach(Data1 o in lst1.Filter(new Data1Filter(), flt1))
{
Console.WriteLine(o);
}
Dictionary<string, string> flt2 = new Dictionary<string, string>();
flt2.Add("A.V1", "6DV1");
flt2.Add("E", "6E");
foreach(Data2 o in lst2.Filter(new Data2Filter(), flt2))
{
Console.WriteLine(o);
}
Console.ReadKey();
}
}
}
Og nu kommer vi til hvor jeg vil undgaa at skulle skrive disse filter klasser ved at generere dem dynamisk.
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;
namespace E
{
public class Data
{
public string V1 { get; set; }
public string V2 { get; set; }
public override string ToString()
{
return string.Format("{{V1={0},V2={1}}}", V1, V2);
}
}
public class Data1
{
public Data A { get; set; }
public string B { get; set; }
public string C { get; set; }
public override string ToString()
{
return string.Format("{{A={0},B={1},C={2}}}", A, B, C);
}
}
public class Data2
{
public Data D { get; set; }
public string E { get; set; }
public override string ToString()
{
return string.Format("{{D={0},E={1}}}", D, E);
}
}
public interface IFilter<T>
{
bool Accept(Dictionary<string, string> filter, T o);
}
public static class Util
{
public static TV Get<TK, TV>(this Dictionary<TK, TV> d, TK key) where TV : class
{
if(d.ContainsKey(key))
{
return d[key];
}
else
{
return null;
}
}
private static Dictionary<string, object> cache = new Dictionary<string, object>();
private static void GetProperties(Type typ, string prefix, List<string> result)
{
foreach(PropertyInfo pi in typ.GetProperties())
{
string propnam = prefix + (prefix.Length > 0 ? "." : "") + pi.Name;
if(pi.PropertyType.FullName.StartsWith("System.")) // <---- consider a better test for whether to recurse
{
result.Add(propnam);
}
else
{
GetProperties(pi.PropertyType, propnam, result);
}
}
}
private const string SOURCE_TEMPLATE = @"using System;
using System.Collections.Generic;
using {2};
namespace Gen
{{
public class {0}Filter : IFilter<{0}>
{{
public bool Accept(Dictionary<string, string> filter, {0} o)
{{
return {1};
}}
}}
}}";
private const string TEST_TEMPLATE = @"(o.{0} == (filter.Get(""{0}"") ?? o.{0}))";
private static object GenerateFilter(Type typ)
{
List<string> proplist = new List<string>();
GetProperties(typ, "", proplist);
StringBuilder sb = new StringBuilder();
foreach(string s in proplist)
{
if(sb.Length > 0)
{
sb.Append(" && ");
}
sb.Append(string.Format(TEST_TEMPLATE, s));
}
string src = string.Format(SOURCE_TEMPLATE, typ.Name, sb.ToString(), typ.Namespace);
//Console.WriteLine(src);
CodeDomProvider comp = new CSharpCodeProvider();
CompilerParameters param = new CompilerParameters();
param.GenerateInMemory = true;
param.ReferencedAssemblies.Add("System.Reflection.dll");
param.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
CompilerResults res = comp.CompileAssemblyFromSource(param, src);
/*
foreach(CompilerError line in res.Errors)
{
Console.WriteLine(line);
}
*/
Assembly asm = res.CompiledAssembly;
return asm.CreateInstance("Gen." + typ.Name + "Filter");
}
public static List<T> Filter<T>(this List<T> lst, Dictionary<string, string> filtervals)
{
Type typ = typeof(T);
IFilter<T> filter;
if(cache.ContainsKey(typ.FullName))
{
filter = (IFilter<T>)cache[typ.FullName];
}
else
{
filter = (IFilter<T>)GenerateFilter(typ);
cache.Add(typ.FullName, filter);
}
return lst.Where(o => filter.Accept(filtervals, o)).ToList();
}
}
public class Program
{
public static void Main(string[] args)
{
List<Data1> lst1 = new List<Data1> { new Data1 { A = new Data { V1 = "1AV1", V2 = "1AV2" }, B = "1B", C = "1C" },
new Data1 { A = new Data { V1 = "2AV1", V2 = "2AV2" }, B = "2B", C = "2C" },
new Data1 { A = new Data { V1 = "3AV1", V2 = "3AV2" }, B = "3B", C = "3C" } };
List<Data2> lst2 = new List<Data2> { new Data2 { D = new Data { V1 = "4DV1", V2 = "4DV2" }, E = "4E" },
new Data2 { D = new Data { V1 = "5DV1", V2 = "5DV2" }, E = "5E" },
new Data2 { D = new Data { V1 = "6DV1", V2 = "6DV2" }, E = "6E" } };
foreach(Data1 o in lst1)
{
Console.WriteLine(o);
}
foreach(Data2 o in lst2)
{
Console.WriteLine(o);
}
Dictionary<string, string> flt1 = new Dictionary<string, string>();
flt1.Add("A.V2", "2AV2");
foreach(Data1 o in lst1.Filter(flt1))
{
Console.WriteLine(o);
}
Dictionary<string, string> flt2 = new Dictionary<string, string>();
flt2.Add("A.V1", "6DV1");
flt2.Add("E", "6E");
foreach(Data2 o in lst2.Filter(flt2))
{
Console.WriteLine(o);
}
Console.ReadKey();
}
}
}
Dett her er rimeligt generisk. Det kraever ikke noget af data klassen. Og jeg tror at det performer godt, da al reflection overheadet og klasse generering kun sker en gang for hver data klasse - ikke ved hver kald af Filter og ikke ved hver element i liste. Til gengaeld er koden saa ikke laengere helt selvindlysende.