问题
Final Edit: I was able to locate the duplicate field in the ini file. Thanks for your help everyone!
I'm using a regular expression to parse an ini file and LINQ to store it in a Dictionary:
Sample Data:
[WindowSettings]
Window X Pos='0'
Window Y Pos='0'
Window Maximized='false'
Window Name='Jabberwocky'
[Logging]
Directory='C:\Rosetta Stone\Logs'
EDIT: Here is the file actually causing the problem: http://pastebin.com/mQSrkrcP
EDIT2: I've narrowed it down to being caused by the last section in the file: [list_first_nonprintable]
For some reason one of the files that I'm parsing with this is throwing this exception:
System.ArgumentException: An item with the same key has already been added.
Is there any way for me to either find out which key is causing the problem (so I can fix the file), or to just skip the key that's causing this and continue parsing?
Here is the code:
try
{
// Read content of ini file.
string data = System.IO.File.ReadAllText(project);
// Create regular expression to parse ini file.
string pattern = @"^((?:\[)(?<Section>[^\]]*)(?:\])(?:[\r\n]{0,}|\Z))((?!\[)(?<Key>[^=]*?)(?:=)(?<Value>[^\r\n]*)(?:[\r\n]{0,4}))*";
//pattern = @"
//^ # Beginning of the line
//((?:\[) # Section Start
// (?<Section>[^\]]*) # Actual Section text into Section Group
// (?:\]) # Section End then EOL/EOB
// (?:[\r\n]{0,}|\Z)) # Match but don't capture the CRLF or EOB
// ( # Begin capture groups (Key Value Pairs)
// (?!\[) # Stop capture groups if a [ is found; new section
// (?<Key>[^=]*?) # Any text before the =, matched few as possible
// (?:=) # Get the = now
// (?<Value>[^\r\n]*) # Get everything that is not an Line Changes
// (?:[\r\n]{0,4}) # MBDC \r\n
// )* # End Capture groups";
// Parse each file into a Dictionary.
Dictionary<string, Dictionary<string, string>> iniFile
= (from Match m in Regex.Matches(data, pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline)
select new
{
Section = m.Groups["Section"].Value,
kvps = (from cpKey in m.Groups["Key"].Captures.Cast<Capture>().Select((a, i) => new { a.Value, i })
join cpValue in m.Groups["Value"].Captures.Cast<Capture>().Select((b, i) => new { b.Value, i }) on cpKey.i equals cpValue.i
select new KeyValuePair<string, string>(cpKey.Value, cpValue.Value)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
}).ToDictionary(itm => itm.Section, itm => itm.kvps);
return iniFile;
}
catch (ArgumentException ex)
{
System.Diagnostics.Debug.Write(ex.ToString());
return new Dictionary<string, Dictionary<string, string>>();
}
Thanks in advance.
回答1:
This just means that when you convert to a Dictionary --
.ToDictionary(itm => itm.Section, itm => itm.kvps);
-- there are multiple keys (itm.Section). You can use ToLookup instead, which is kind of like a dictionary but allows multiple keys.
Edit
There are a couple of ways to call ToLookup. The simplest is to specify the key selector:
var lookup =
// ...
.ToLookup(itm => itm.Section);
This should provide a lookup where the key is of type Group. Getting a lookup value should then return an IEnumerable, where T is the anonymous type:
Group g = null;
// TODO get group
var lookupvalues = lookup[g];
If the .NET compiler doesn't like this (sometimes it seems to have trouble figuring out what the various types should be), you can also specify an element selector, for example:
ILookup<string, KeyValuePair<string,string>> lookup =
// ...
.ToLookup(
itm => itm.Section.Value, // key selector
itm => itm.kvps // element selector
);
回答2:
You can write your own ToDictionary method that doesn't break with duplicate keys easy enough.
public static Dictionary<K,V> ToDictionary<TSource, K, V>(
this IEnumerable<TSource> source,
Func<TSource, K> keySelector,
Funct<TSource, V> valueSelector)
{
//TODO validate inputs for null arguments.
Dictionary<K,V> output = new Dictionary<K,V>();
foreach(TSource item in source)
{
//overwrites previous values
output[keySelector(item)] = valueSelector(item);
//ignores future duplicates, comment above and
//uncomment below to change behavior
//K key = keySelector(item);
//if(!output.ContainsKey(key))
//{
//output.Add(key, valueSelector(item));
//}
}
return output;
}
I assume that you could figure out how to implement the additional overloads (without value the selector).
回答3:
You can use Tuple
to pass multiple keys. Check sample code below:
.ToDictionary(k => new Tuple<string,string>(k.key1,k.key2), v => v.value)
来源:https://stackoverflow.com/questions/10625879/c-sharp-linq-todictionary-key-already-exists