jian.wu
jian.wu

Reputation: 85

c# NameValueCollection distinct values

C# code as below:

NameValueCollection nvc = new NameValueCollection();
nvc.Add("color", "red");
nvc.Add("size", "s");
nvc.Add("color", "red");
nvc.Add("size", "m");
nvc.Add("color", "red");
nvc.Add("size", "l");

//blue
nvc.Add("color", "blue");
nvc.Add("size", "m");
nvc.Add("color", "blue");
nvc.Add("size", "l");
//green
nvc.Add("color", "green");
nvc.Add("size", "s");
nvc.Add("color", "green");
nvc.Add("size", "m");
nvc.Add("color", "green");
nvc.Add("size", "l");

actual result:

color:red,red,red,blue,blue,green,green,green 
size:s,m,l,m,l,s,m,l 

hope result:

color:red,blue,green     
size:s,m,l 

Upvotes: 2

Views: 2062

Answers (3)

Mark M
Mark M

Reputation: 39

Old question but I needed something similar(in VB.Net but easily applicable in C#); am using the collection to compile references between words/word groups and values while ignoring case. The dictionary solutions overwrite values, maintaining only the last value and a dictionary tied to a list becomes more plumbing maintenance than I like to bother with. To get exactly what is being asked, in a way that worked for me I first used the following syntax to add:

If Not nvc.AllKeys.Contains(name) OrElse Not nvc.GetValues(name).Contains(value) Then nvc.Add(name, value)

Large numbers of repeated calls to the same keys and values wont be very efficient but this is probably the simplest way I could find. This ended up being redundant and in my code name(and sometimes even value)was being built ad-hoc; in order to reduce code/variables and increase efficiency as much as I could(still using this somewhat inefficient method), I first considered building an <Extension()> Sub but instead created this:

Private Class NoDupValueNameValueCollection
    Inherits Collections.Specialized.NameValueCollection
    Public Overrides Sub Add(name As String, value As String)
        If Not MyBase.AllKeys.Contains(name) OrElse Not MyBase.GetValues(name).Contains(value) Then MyBase.Add(name, value)
    End Sub
End Class

or C#

    private class NoDupValueNameValueCollection : System.Collections.Specialized.NameValueCollection {
        public override void Add(string name, string value) {
            if (!(base.AllKeys.Contains(name)) || !(base.GetValues(name).Contains(value))) base.Add(name, value);
        }
    }

It can now be used in place of and exactly as nvc and will prevent duplicate values. In fact it is exactly what is being asked: a NameValueCollection that prevents the addition of duplicate values

Upvotes: 0

ErikE
ErikE

Reputation: 50271

Some thoughts for you:

  1. NameValueCollection allows duplicate values. Use a Dictionary<string, string>.
  2. NameValueCollection is an older object that is strongly typed to string, string only. Dictionary is more flexible, because you can specify the type parameters.
  3. When using Dictionary note that you will get errors if you try to add the same key twice. This can be remedied by changing your syntax slightly. Instead of Add(), just use the indexer:

    var myDict = new Dictionary<string, string>();
    myDict["color"] = "blue";
    myDict["color"] = "blue"; // no harm doing this twice
    

In my experience, using strings all over the place is a way to let bugs creep in, and makes it easy to make mistakes (hmmm, wonder how I know this?). It's too easy to misspell something, or assign a size to a color or vice versa.

Is there a reason you can't strongly type the concepts in your code?

public abstract class ProductAttribute { }

public sealed class Color : ProductAttribute {
    private Color(string value) { Value = value; }
    public Value { get; }

    public static readonly Blue = new Color("blue");
    public static readonly Green = new Color("green");

    private static readonly IReadOnlyDictionary<string, Color> _colorDict = 
       new ReadOnlyDictionary<string, Color>(
          new List<Color> {
             Blue, Green
          }.ToDictionary(color => color.Value, color => color)
       );

    public static Color GetColor(string value) {
       Color color;
       _colorDict.TryGetValue(value, out color);
       return color;
    }
}

Then do something similar for sizes. This is just one idea, perhaps not very flexible (you have to change the code to add new values), but it should show you what's possible.

But look what you can do now:

var attrs = new HashSet<ProductAttribute>;
attrs.Add(Color.Blue);
attrs.Add(Color.Green);
attrs.Add(Color.Blue); // no problem
attrs.Add(Color.GetColor("blue")); // no problem
attrs.Add(Size.Small);
attrs.Add(Size.Medium);
attrs.Add(Size.Medium); // no problem

One way to work with the ProductAttribute classes is like so:

foreach (ProductAttribute attr in attrs) {
   Color color = attr as Color;
   if (color != null) {
      // work with color
   }
}

Alternately, you can create a ProductAttributeType enum, add it as a parameter in the ProductAttribute constructor and expose it in a read-only abstract property in the ProductAttribute class. Then all your subclasses pass in what type they are, and you can easily get the type of something just doing attr.ProductAttributeType == ProductAttributeType.Color for example.

Upvotes: 2

Hari Prasad
Hari Prasad

Reputation: 16956

I'm not sure on your use case, NameValueCollection allows duplicates better to use Dictionary for such cases.

Still you could do something like this on NameValueCollection to get the results you want.

var results = nvc.AllKeys
    .GroupBy(g=>g)
    .Select(s=> 
            new 
            {
                key = s.Key,  
                values = nvc.GetValues(s.Key).Distinct() 
            }); 


foreach(var result in results)
{
    Console.WriteLine("{0}- {1}", result.key, string.Join(",", result.values) ;
}

Working Example

Upvotes: 1

Related Questions