Refactoring two methods down to one - c#

I have two methods that almost do the same thing. They get a List<XmlNode> based on state OR state and schoolType and then return a distinct, ordered IEnumerable<KeyValuePair<string,string>>. I know they can be refactored but I'm struggling to determine what type the parameter should be for the linq statement in the return of the method (the last line of each method).
I thank you for your help in advance.
private IEnumerable<KeyValuePair<string, string>> getAreaDropDownDataSource() {
StateInfoXmlDocument stateInfoXmlDocument = new StateInfoXmlDocument();
string schoolTypeXmlPath = string.Format(STATE_AND_SCHOOL_TYPE_XML_PATH, StateOfInterest, ConnectionsLearningSchoolType);
var schoolNodes = new List<XmlNode>(stateInfoXmlDocument.SelectNodes(schoolTypeXmlPath).Cast<XmlNode>());
return schoolNodes.Select(x => new KeyValuePair<string, string>(x.Attributes["idLocation"].Value, x.Value)).OrderBy(x => x.Key).Distinct();
}
private IEnumerable<KeyValuePair<string, string>> getStateOfInterestDropDownDataSource() {
StateInfoXmlDocument stateInfoXmlDocument = new StateInfoXmlDocument();
string schoolTypeXmlPath = string.Format(SCHOOL_TYPE_XML_PATH, ConnectionsLearningSchoolType);
var schoolNodes = new List<XmlNode>(stateInfoXmlDocument.SelectNodes(schoolTypeXmlPath).Cast<XmlNode>());
return schoolNodes.Select(x => new KeyValuePair<string, string>(x.Attributes["stateCode"].Value, x.Attributes["stateName"].Value)).OrderBy(x => x.Key).Distinct();
}

Extract nodes retrieving to separate methods/properties. I also suggest to have different properties/methods for extracting school and state nodes:
private List<XmlNode> GetNodes(string xPath)
{
XmlDocument stateInfoXmlDocument = new XmlDocument();
return new List<XmlNode>(stateInfoXmlDocument.SelectNodes(xPath)
.Cast<XmlNode>());
}
private List<XmlNode> SchoolNodes
{
get { return GetNodes(String.Format(SCHOOL_PATH, LearningSchoolType)); }
}
private List<XmlNode> StateNodes
{
get { return GetNodes(String.Format(STATE_PATH, StateOfInterest)); }
}
Use union of school and state nodes for retrieving area nodes:
private IEnumerable<KeyValuePair<string, string>> GetAreaDropDownDataSource()
{
return SchoolNodes.Union(StateNodes)
.Select(x => new KeyValuePair<string, string>(x.Attributes["idLocation"].Value, x.Value))
.OrderBy(x => x.Key)
.Distinct();
}
private IEnumerable<KeyValuePair<string, string>> GetStateOfInterestDropDownDataSource()
{
return SchoolNodes
.Select(x => new KeyValuePair<string, string>(x.Attributes["stateCode"].Value, x.Attributes["stateName"].Value))
.OrderBy(x => x.Key)
.Distinct();
}
Also you can use different selectors of type Func<XmlNode, KeyValuePair<string, string>> and pass them to method which will create data source:
private IEnumerable<KeyValuePair<string, string>> GetDropDownDataSource(
List<XmlNode> nodes,
Func<XmlNode, KeyValuePair<string, string>> selector)
{
return nodes.Select(selector)
.OrderBy(x => x.Key)
.Distinct();
}

I feel like while they are both returning an IEnumerable<KeyValuePair<string,string>>, these methods are semantically quite different in content. Therefore, I would keep the two methods and extract only the repeated code to a third. Something like:
private List<XmlNode> getSchoolNodes(string xmlPath, params object[] values)
{
StateInfoXmlDocument stateInfoXmlDocument = new StateInfoXmlDocument();
string schoolTypeXmlPath = string.Format(xmlPath, values);
return new List<XmlNode>(stateInfoXmlDocument.SelectNodes(schoolTypeXmlPath).Cast<XmlNode>());
}
private IEnumerable<KeyValuePair<string, string>> getAreaDropDownDataSource() {
var schoolNodes = getSchoolNodes(STATE_AND_SCHOOL_TYPE_XML_PATH, StateOfInterest, ConnectionsLearningSchoolType);
return schoolNodes.Select(x => new KeyValuePair<string, string>(x.Attributes["idLocation"].Value, x.Value)).OrderBy(x => x.Key).Distinct();
}
private IEnumerable<KeyValuePair<string, string>> getStateOfInterestDropDownDataSource() {
var schoolNodes = getSchoolNodes(SCHOOL_TYPE_XML_PATH, ConnectionsLearningSchoolType);
return schoolNodes.Select(x => new KeyValuePair<string, string>(x.Attributes["stateCode"].Value, x.Attributes["stateName"].Value)).OrderBy(x => x.Key).Distinct();
}
You could go as far as the following, but I wonder if this is overengineering the problem and creating overhead calling the two Funcs.
private IEnumerable<KeyValuePair<string, string>> getSchoolNodeDataSource(Func<XmlNode, string> keyFunc, Func<XmlNode, string> valueFunc, string xmlPath, params object[] values)
{
StateInfoXmlDocument stateInfoXmlDocument = new StateInfoXmlDocument();
string schoolTypeXmlPath = string.Format(xmlPath, values);
var schoolNodes = new List<XmlNode>(stateInfoXmlDocument.SelectNodes(schoolTypeXmlPath).Cast<XmlNode>());
return schoolNodes.Select(x => new KeyValuePair<string, string>(keyFunc(x), valueFunc(x))).OrderBy(x => x.Key).Distinct();
}
private IEnumerable<KeyValuePair<string, string>> getAreaDropDownDataSource() {
return getSchoolNodeDataSource(x => x.Attributes["idLocation"].Value, x => x.Value,
STATE_AND_SCHOOL_TYPE_XML_PATH, StateOfInterest, ConnectionsLearningSchoolType);
}
private IEnumerable<KeyValuePair<string, string>> getStateOfInterestDropDownDataSource() {
return getSchoolNodeDataSource(x => x.Attributes["stateCode"].Value, x => x.Attributes["stateName"].Value,
SCHOOL_TYPE_XML_PATH, ConnectionsLearningSchoolType);
}

private IEnumerable<KeyValuePair<string, string>> Foo(
string schoolTypeXmlPath,
Func<T, string> keySelector,
Func<T, string> valueSelector)
{
return (
from XmlNode x in StateInfoXmlDocument().SelectNodes(schoolTypeXmlPath)
orderby x.Key
select new KeyValuePair<string, string>(keySelector(x), valueSelector(x)))
.Distinct()
}
private IEnumerable<KeyValuePair<string, string>> getAreaDropDownDataSource() {
return Foo(
string.Format(STATE_AND_SCHOOL_TYPE_XML_PATH, StateOfInterest, ConnectionsLearningSchoolType),
x => x.Attributes["idLocation"].Value,
x => x.Value);
}
private IEnumerable<KeyValuePair<string, string>> getStateOfInterestDropDownDataSource() {
return Foo(
string.Format(SCHOOL_TYPE_XML_PATH, ConnectionsLearningSchoolType),
x => x.Attributes["stateCode"].Value,
x => x.Attributes["stateName"].Value);
}

Related

How do you use ToDictionary with IEnumerable<string>> as one of the types?

When I try to use .ToDictionary() to convert Dictionary<string, string> to a Dictionary<string, IEnumerable<string>> I get an error
public Dictionary<string, IEnumerable<string>> ConvertDictionary(Dictionary<string, string> data)
{
return data.ToDictionary(x => x.Key, y => new List<string> { y.Value });
}
The error I get is "Cannot Implicitly Cast Dictionary<string, List<string>> to Dictionary<string, IEnumerable<string>>
I tried this, but you can't create an instance of an abstract type
return data.ToDictionary(x => x.Key, y => new IEnumerable<string> { y.Value });
I tried this, but .ToDictionary() doesn't let you specify the types this way
return data.ToDictionary<string, IEnumerable<string>>(x => x.Key, y => new List<string> { y.Value });
Use "as" in the Linq statement to convert it
public Dictionary<string, IEnumerable<string>> ConvertDictionary(Dictionary<string, string> data)
{
return data.ToDictionary(x => x.Key, y => new List<string> { y.Value } as IEnumerable<string>);
}

Nested foreach to LINQ in multi-level dictionary

I would like to simplify below nested foreach loops using LINQ but couldn't figure out the way. I guess I can use SelectMany using lambda but not sure. I want to create list of objects of ClassA after this nested iteration. Any help is appreciated:
public List<ClassA> GetLists(Dictionary<string, Dictionary<IEnumerable, Dictionary<string, ClassB>>> groups)
{
var retOutput = new List<ClassA>();
foreach (KeyValuePair<string, Dictionary<IEnumerable, Dictionary<string, ClassB>>> group1 in groups)
{
foreach (KeyValuePair<IEnumerable, Dictionary<string, ClassB>> group2 in group1.Value)
{
foreach (KeyValuePair<string, ClassB> group3 in group2.Value)
{
GetList(retOutput, group1.Key,
group2.Key,
group3);
}
}
}
return retOutput;
}
private static void GetList(List<ClassA> retOutput,
string group1Key,
IEnumerable group2Key,
KeyValuePair<string, ClassB> group3)
{
List<List<string>> itemIdsLists = group3.Value.ItemId.IntoChunks(2000);
foreach (var itemIdList in itemIdsLists)
{
var currentRequest = new ClassA
{
TransactionType = group1Key,
Filters = new Dictionary<string, object>(),
ItemIds = new List<string>(),
PropStreamsDict = new Dictionary<string, Tuple<long, string>>()
};
if (group2Key is Dictionary<string, object>)
{
currentRequest.Filters = (Dictionary<string, object>)group2Key;
}
currentRequest.PropStreamsDict.Add(group3.Key, Tuple.Create(group3.Value.StreamId,
group3.Value.Uom));
currentRequest.ItemIds.AddRange(itemIdList);
retOutput.Add(currentRequest);
}
}
You should use SelectMany to make nested foreach.
Here what I come up with:
public List<ClassA> GetLists(Dictionary<string, Dictionary<IEnumerable, Dictionary<string, ClassB>>> groups)
{
return groups
.SelectMany(grp1 => grp1.Value
.SelectMany(grp2 => grp2.Value
.SelectMany(grp3 => grp3.Value.ItemId
.IntoChunks(2000)
.Select(itemIdList =>
new ClassA
{
TransactionType = grp1.Key,
Filters = grp2.Key is Dictionary<string, object> ?
(Dictionary<string, object>)grp2.Key :
new Dictionary<string, object>(),
ItemIds = new List<string>(itemIdList),
PropStreamsDict = new Dictionary<string, Tuple<long, string>>
{
{ grp3.Key, Tuple.Create(grp3.Value.StreamId, grp3.Value.Uom) }
}
}
)
)
)
)
.ToList();
}
You didn't post your ClassA and ClassB so I had to guess.

Consolidate similar methods into a single method to reduce duplication

I'm trying to learn and practice OOP principles and I need some help with an example to get me over the hump. I have the following code:
using System.Collections.Generic;
namespace Test
{
class Program
{
static void Main()
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("cat", "one");
dictionary.Add("dog", "two");
dictionary.Add("llama", "three");
dictionary.Add("iguana", "four");
var test1 = GetKVP(dictionary, "llama");
var test2 = GetValue(dictionary, "llama");
var test3 = GetPosition(dictionary, "llama");
}
static KeyValuePair<string, string> GetKVP(Dictionary<string, string> dict, string key_to_find)
{
foreach (KeyValuePair<string, string> kvp in dict)
{
if (kvp.Key == key_to_find)
{return kvp;}
}
return new KeyValuePair<string, string>();
}
static string GetValue(Dictionary<string, string> dict, string key_to_find)
{
foreach (KeyValuePair<string, string> kvp in dict)
{
if (kvp.Key == key_to_find)
{return kvp.Value;}
}
return string.Empty;
}
static int GetPosition(Dictionary<string, string> dict, string key_to_find)
{
int counter = 0;
foreach (KeyValuePair<string, string> kvp in dict)
{
if (kvp.Key == key_to_find)
{return counter;}
counter += 1;
}
return -1;
}
}
}
What I'm trying to do is consolidate the code set so that I can have a single method which returns a different data type without duplicating code. Please don't comment on the fact that there are several more efficient ways to search a dictionary, I'm aware that this is not ideal.. I simply mocked up some data and methods to use as an example. For the life of me, I can't really visualize how to implement something like that.
You could try doing this, but I don't think it helps too much:
static R GetResult<R>(Dictionary<string, string> dict, string key_to_find, Func<KeyValuePair<string, string>, R> selector, R otherwise)
{
return dict.Where(kvp => kvp.Key == key_to_find).Select(kvp => selector(kvp)).DefaultIfEmpty(otherwise).First();
}
static KeyValuePair<string, string> GetKVP(Dictionary<string, string> dict, string key_to_find)
{
return GetResult(dict, key_to_find, kvp => kvp, new KeyValuePair<string, string>());
}
static string GetValue(Dictionary<string, string> dict, string key_to_find)
{
return GetResult(dict, key_to_find, kvp => kvp.Value, String.Empty);
}
static int GetPosition(Dictionary<string, string> dict, string key_to_find)
{
return dict.Where(kvp => kvp.Key == key_to_find).Select((kvp, n) => n).DefaultIfEmpty(-1).First();
}
In .net everthing is based on Object, so just return Object and then object could be anything just as you want
here is a sample based on your code
using System.Collections.Generic;
namespace Test
{
class Program
{
static void Main()
{
Dictionary<string, Object> dictionary = new Dictionary<string, string>();
dictionary.Add("cat", "one");
dictionary.Add("dog", "two");
dictionary.Add("llama", "three");
dictionary.Add("iguana", "four");
var test1 = GetWhatEver(dictionary, "llama");
var test2 = GetWhatEver(dictionary, "llama");
var test3 = GetWhatEver(dictionary, "llama");
}
static Object GetWhatEver(Dictionary<string, Object> dict, string key_to_find)
{
foreach (var kvp in dict)
{
if (kvp.Key == key_to_find)
{return kvp.Value;}
}
return null;
}
}
}

Merging generic method

I'm new to C# and I have the following 3 methods. These methods allow the caller to retrieve differents properties of the table by specifying a lambda expression on the given method. However, I have the feeling an expert would combine them even further into a single generic method. If this is possible, please let me know how.
private KeyValuePair<T, int> GetHeaderProperty<T>(Func<Header, T> Property,
Source ds)
{
Func<Source, Header> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId)
.First().Header;
return new KeyValuePair<T,int>(Property(GetValue(ds)), 0);
}
private KeyValuePair<T, int> GetBookProperty<T>(Func<Book, T> Property,
Source ds)
{
Func<Source, Book> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId).First();
return new KeyValuePair<T, int>(Property(GetValue(ds)), 0);
}
private KeyValuePair<T, int> GetFleetProperty<T>(Func<Fleet, T> Property,
Source ds)
{
Func<Source,Fleet> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId).First()
.Header.Fleet;
return new KeyValuePair<T,int>(Property(GetValue(ds)), 0);
}
I think the following will be equivalent to calling all three methods in a row and adding the results to a list:
private IEnumerable<KeyValuePair<T, int>> GetFleetProperty<T>(
Func<Book, T> PropertyBook,
Func<Header, T> PropertyHeader,
Func<Fleet, T> PropertyFleet,
Source ds)
{
Func<Source,Fleet> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId).First();
var book = GetValue(ds);
var result = new List<KeyValuePair<T, int>>();
result.Add(new KeyValuePair<T, int>(PropertyBook(book), 0);
result.Add(new KeyValuePair<T, int>(PropertyHeader(book.Header), 0);
result.Add(new KeyValuePair<T, int>(PropertyFleet(book.Header.Fleet), 0);
return result;
}
UPDATE:
You could also create a method like this:
private KeyValuePair<T, int> GetProperty<T, TProperty>(
Func<TProperty, T> Property,
Func<Book, TProperty> GetProperty,
Source ds)
{
Func<Source, Header> GetValue =
a => Books.Where(str => str.BookId == a.DiscardBookId)
.First();
var book = GetValue(ds);
return new KeyValuePair<T,int>(Property(GetProperty(book)), 0);
}
You would call it like this for Header:
GetProperty(xyz, b => b.Header, ds);
You would call it like this for Book:
GetProperty(xyz, b => b, ds);
You would call it like this for Fleet:
GetProperty(xyz, b => b.Header.Fleet, ds);
You can use some thing like this
public interface IPieceProvider<T>
{
T GetPiece();
}
public class Fleet
{
public string Test;
}
public class Header
{
public Fleet Fleet;
public string Test;
}
public class Source
{
public int DiscardBookId;
}
public partial class Book
: IPieceProvider<Book>, IPieceProvider<Header>, IPieceProvider<Fleet>
{
public int BookId;
public Header Header;
public string Test;
Book IPieceProvider<Book>.GetPiece()
{
return this;
}
Header IPieceProvider<Header>.GetPiece()
{
return Header;
}
Fleet IPieceProvider<Fleet>.GetPiece()
{
return Header.Fleet;
}
}
class Program
{
Book[] Books;
private KeyValuePair<T, int> GetProperty<T, TP>(Func<TP, T> propertyGetter, Source ds)
{
return Books
.Where(b => b.BookId == ds.DiscardBookId)
.Cast<IPieceProvider<TP>>()
.Select(p => p.GetPiece())
.Select(p => new KeyValuePair<T, int>(propertyGetter(p), 0))
.First();
}
static void Main(string[] args)
{
var source = new Source();
var prg = new Program();
var bookTest = prg.GetProperty((Book b) => b.Test, source);
var headerTest = prg.GetProperty((Header h) => h.Test, source);
var fleetTest = prg.GetProperty((Fleet f) => f.Test, source);
Console.ReadKey();
}
}

Serializing Request.Form to a Dictionary or something

Hi i need to pass my Request.Form as a parameter, but first i have to add some key/value pairs to it. I get the exception that the Collection is readonly.
I've tried:
System.Collections.Specialized.NameValueCollection myform = Request.Form;
and i get the same error.
and i've tried:
foreach(KeyValuePair<string, string> pair in Request.Form)
{
Response.Write(Convert.ToString(pair.Key) + " - " + Convert.ToString(pair.Value) + "<br />");
}
to test if i can pass it one by one to another dictionary, but i get:
System.InvalidCastException: Specified
cast is not valid.
some help, anyone? Thanx
You don't need to cast a string to string. NameValueCollection is built around string keys, and string values. How about a quick extension method:
public static IDictionary<string, string> ToDictionary(this NameValueCollection col)
{
var dict = new Dictionary<string, string>();
foreach (var key in col.Keys)
{
dict.Add(key, col[key]);
}
return dict;
}
That way you can easily go:
var dict = Request.Form.ToDictionary();
dict.Add("key", "value");
If your already using MVC then you can do it with 0 lines of code.
using System.Web.Mvc;
var dictionary = new Dictionary<string, object>();
Request.Form.CopyTo(dictionary);
Andre,
how about:
System.Collections.Specialized.NameValueCollection myform = Request.Form;
IDictionary<string, string> myDictionary =
myform.Cast<string>()
.Select(s => new { Key = s, Value = myform[s] })
.ToDictionary(p => p.Key, p => p.Value);
uses LINQ to keep it all on one 'line'. this could be exrapolated to an extension method of:
public static IDictionary<string, string> ToDictionary(this NameValueCollection col)
{
IDictionary<string, string> myDictionary = new Dictionary<string, string>();
if (col != null)
{
myDictionary =
col.Cast<string>()
.Select(s => new { Key = s, Value = col[s] })
.ToDictionary(p => p.Key, p => p.Value);
}
return myDictionary;
}
hope this helps..
public static IEnumerable<Tuple<string, string>> ToEnumerable(this NameValueCollection collection)
{
return collection
//.Keys
.Cast<string>()
.Select(key => new Tuple<string, string>(key, collection[key]));
}
or
public static Dictionary<string, string> ToDictionary(this NameValueCollection collection)
{
return collection
//.Keys
.Cast<string>()
.ToDictionary(key => key, key => collection[key]));
}

Categories

Resources