Làm cách nào để truy xuất mục thực tế từ HashSet <T>?


85

Tôi đã đọc câu hỏi này về lý do tại sao không thể thực hiện được, nhưng chưa tìm ra giải pháp cho vấn đề.

Tôi muốn lấy một mục từ .NET HashSet<T>. Tôi đang tìm một phương pháp có chữ ký này:

/// <summary>
/// Determines if this set contains an item equal to <paramref name="item"/>, 
/// according to the comparison mechanism that was used when the set was created. 
/// The set is not changed. If the set does contain an item equal to 
/// <paramref name="item"/>, then the item from the set is returned.
/// </summary>
bool TryGetItem<T>(T item, out T foundItem);

Tìm kiếm tập hợp cho một mục với phương pháp như vậy sẽ là O (1). Cách duy nhất để lấy một mục từ a HashSet<T>là liệt kê tất cả các mục là O (n).

Tôi không tìm thấy bất kỳ giải pháp nào cho vấn đề này ngoài việc tự tạo HashSet<T>hoặc sử dụng a Dictionary<K, V>. Bất kỳ ý tưởng nào khác?

Lưu ý:
Tôi không muốn kiểm tra xem liệu HashSet<T>có chứa mục hay không. Tôi muốn nhận tham chiếu đến mục được lưu trữ trong HashSet<T>vì tôi cần cập nhật nó (mà không thay thế nó bằng một phiên bản khác). Mục mà tôi chuyển tới TryGetItemsẽ bằng (theo cơ chế so sánh mà tôi đã chuyển cho hàm tạo) nhưng nó sẽ không phải là cùng một tham chiếu.


1
Tại sao không sử dụng Chứa và trả lại mục bạn đã chuyển làm đầu vào?
Mathias


2
Nếu bạn cần phải nhìn lên một đối tượng dựa trên một giá trị quan trọng, sau đó Dictionary <T> có thể là bộ sưu tập phù hợp hơn để lưu trữ nó trong.
ThatBlairGuy

@ThatBlairGuy: Bạn nói đúng. Tôi nghĩ rằng tôi sẽ triển khai bộ sưu tập Set của riêng mình bằng cách sử dụng Từ điển trong nội bộ để lưu trữ các mục của tôi. Chìa khóa sẽ là HashCode của mặt hàng. Tôi sẽ có hiệu suất tương đương với HashSet và nó sẽ giúp tôi tiết kiệm việc phải cung cấp khóa mỗi khi tôi cần thêm / bớt / lấy một mục từ bộ sưu tập của mình.
Francois C

2
@mathias Vì hashset có thể chứa một mục bằng với đầu vào, nhưng thực tế không giống nhau. Ví dụ: bạn có thể muốn có một tập hợp các kiểu tham chiếu nhưng bạn muốn so sánh nội dung, không phải tham chiếu để bình đẳng.
NounVerber

Câu trả lời:


25

Những gì bạn yêu cầu đã được thêm vào .NET Core một năm trướcgần đây đã được thêm vào .NET 4.7.2 :

Trong .NET Framework 4.7.2, chúng tôi đã thêm một số API vào các loại Bộ sưu tập tiêu chuẩn sẽ kích hoạt chức năng mới như sau.
- 'TryGetValue' được thêm vào SortedSet và HashSet để phù hợp với mẫu Thử được sử dụng trong các loại bộ sưu tập khác.

Chữ ký như sau (tìm thấy trong .NET 4.7.2 trở lên):

    //
    // Summary:
    //     Searches the set for a given value and returns the equal value it finds, if any.
    //
    // Parameters:
    //   equalValue:
    //     The value to search for.
    //
    //   actualValue:
    //     The value from the set that the search found, or the default value of T when
    //     the search yielded no match.
    //
    // Returns:
    //     A value indicating whether the search was successful.
    public bool TryGetValue(T equalValue, out T actualValue);

Tái bút : Trong trường hợp bạn quan tâm, có một chức năng liên quan mà họ sẽ thêm vào trong tương lai - HashSet.GetOrAdd (T).


65

Đây thực sự là một thiếu sót rất lớn trong bộ sưu tập. Bạn sẽ chỉ cần một Từ điển các khóa hoặc một HashSet cho phép truy xuất các tham chiếu đối tượng. Rất nhiều người đã yêu cầu nó, tại sao nó không được sửa chữa là ngoài tôi.

Nếu không có thư viện của bên thứ ba, giải pháp tốt nhất là sử dụng Dictionary<T, T>các khóa giống hệt giá trị, vì Từ điển lưu trữ các mục nhập của nó dưới dạng bảng băm. Về mặt hiệu suất, nó giống như HashSet, nhưng nó lãng phí bộ nhớ (kích thước của một con trỏ trên mỗi mục nhập).

Dictionary<T, T> myHashedCollection;
...
if(myHashedCollection.ContainsKey[item])
    item = myHashedCollection[item]; //replace duplicate
else
    myHashedCollection.Add(item, item); //add previously unknown item
...
//work with unique item

1
Tôi đề nghị rằng các khóa trong từ điển của anh ấy nên là bất cứ thứ gì anh ấy hiện đã đặt trong EqualityComparer của mình cho hashset. Tôi cảm thấy thật tệ khi sử dụng EqualityComparer khi bạn không thực sự nói rằng các mục là bằng nhau (nếu không, bạn có thể chỉ sử dụng mục bạn đã tạo cho mục đích so sánh). Tôi muốn tạo một lớp / cấu trúc đại diện cho khóa. Tất nhiên, điều này phải trả giá bằng nhiều bộ nhớ hơn.
Ed T

1
Vì khóa được lưu trữ bên trong Giá trị, tôi khuyên bạn nên sử dụng bộ sưu tập được kế thừa từ KeyedCollection thay vì Từ điển. msdn.microsoft.com/en-us/library/ms132438(v=vs.110).aspx
Truy cập Bị Từ chối

11

Phương pháp này đã được thêm vào .NET Framework 4.7.2 (và .NET Core 2.0 trước nó); xem HashSet<T>.TryGetValue. Trích dẫn nguồn :

/// <summary>
/// Searches the set for a given value and returns the equal value it finds, if any.
/// </summary>
/// <param name="equalValue">The value to search for.
/// </param>
/// <param name="actualValue">
/// The value from the set that the search found, or the default value
/// of <typeparamref name="T"/> when the search yielded no match.</param>
/// <returns>A value indicating whether the search was successful.</returns>
/// <remarks>
/// This can be useful when you want to reuse a previously stored reference instead of 
/// a newly constructed one (so that more sharing of references can occur) or to look up
/// a value that has more complete data than the value you currently have, although their
/// comparer functions indicate they are equal.
/// </remarks>
public bool TryGetValue(T equalValue, out T actualValue)

1
Đối với SortedSet cũng vậy.
nawfal

4

Điều gì về việc nạp chồng cho trình so sánh bình đẳng chuỗi:

  class StringEqualityComparer : IEqualityComparer<String>
{
    public string val1;
    public bool Equals(String s1, String s2)
    {
        if (!s1.Equals(s2)) return false;
        val1 = s1;
        return true;
    }

    public int GetHashCode(String s)
    {
        return s.GetHashCode();
    }
}
public static class HashSetExtension
{
    public static bool TryGetValue(this HashSet<string> hs, string value, out string valout)
    {
        if (hs.Contains(value))
        {
            valout=(hs.Comparer as StringEqualityComparer).val1;
            return true;
        }
        else
        {
            valout = null;
            return false;
        }
    }
}

Và sau đó khai báo HashSet là:

HashSet<string> hs = new HashSet<string>(new StringEqualityComparer());

Đây là tất cả về quản lý bộ nhớ - trả về mục thực tế có trong bộ băm chứ không phải là một bản sao giống hệt nhau. Vì vậy, trong đoạn mã trên, chúng tôi tìm thấy chuỗi có cùng nội dung và sau đó trả về một tham chiếu đến chuỗi này. Đối với chuỗi, điều này tương tự như những gì interning làm.
mp666

@zumalifeguard @ mp666 điều này không được đảm bảo hoạt động như hiện tại. Nó sẽ yêu cầu ai đó khởi tạo HashSetđể cung cấp công cụ chuyển đổi giá trị cụ thể. Một giải pháp tối ưu sẽ là TryGetValuetruyền vào một phiên bản mới của chuyên biệt StringEqualityComparer(nếu không, nó as StringEqualityComparercó thể dẫn đến giá trị rỗng khiến .val1quyền truy cập thuộc tính bị ném). Khi làm như vậy, StringEqualityComparer có thể trở thành một lớp riêng lồng nhau trong HashSetExtension. Ngoài ra, trong trường hợp trình so sánh bình đẳng bị ghi đè, StringEqualityComparer sẽ được gọi thành mặc định.
Graeme Wicksted

bạn cần phải khai báo HashSet của bạn như: HashSet <string> valueCash = HashSet mới <string> (StringEqualityComparer mới ())
mp666

1
Hack bẩn thỉu. Tôi biết làm thế nào nó hoạt động nhưng lười biếng của nó chỉ làm cho nó hoạt loại giải pháp
M.kazem Akhgary

2

Được rồi, bạn có thể làm như thế này

YourObject x = yourHashSet.Where(w => w.Name.Contains("strin")).FirstOrDefault();

Điều này là để có được một Phiên bản mới của đối tượng đã chọn. Để cập nhật đối tượng của bạn, bạn nên sử dụng:

yourHashSet.Where(w => w.Name.Contains("strin")).FirstOrDefault().MyProperty = "something";

Đây là một cách thú vị, bạn chỉ cần kết thúc lần thử thứ hai - để nếu bạn tìm kiếm thứ gì đó không có trong danh sách, bạn sẽ nhận được NullReferenceExpection. Nhưng đó là một bước đi đúng hướng?
Piotr Kula

11
LINQ duyệt qua bộ sưu tập trong một vòng lặp foreach, tức là thời gian tra cứu O (n). Mặc dù nó là một giải pháp cho vấn đề, nhưng nó loại bỏ mục đích của việc sử dụng HashSet ngay từ đầu.
Niklas Ekman,


2

Một Thủ thuật khác sẽ làm Phản chiếu, bằng cách truy cập chức năng bên trong InternalIndexOfcủa HashSet. Hãy nhớ rằng các tên trường được mã hóa cứng, vì vậy nếu những tên đó thay đổi trong các phiên bản .NET sắp tới, điều này sẽ bị hỏng.

Lưu ý: Nếu bạn sử dụng Mono, bạn nên thay đổi tên trường từ m_slotsthành _slots.

internal static class HashSetExtensions<T>
{
    public delegate bool GetValue(HashSet<T> source, T equalValue, out T actualValue);

    public static GetValue TryGetValue { get; }

    static HashSetExtensions() {
        var targetExp = Expression.Parameter(typeof(HashSet<T>), "target");
        var itemExp   = Expression.Parameter(typeof(T), "item");
        var actualValueExp = Expression.Parameter(typeof(T).MakeByRefType(), "actualValueExp");

        var indexVar = Expression.Variable(typeof(int), "index");
        // ReSharper disable once AssignNullToNotNullAttribute
        var indexExp = Expression.Call(targetExp, typeof(HashSet<T>).GetMethod("InternalIndexOf", BindingFlags.NonPublic | BindingFlags.Instance), itemExp);

        var truePart = Expression.Block(
            Expression.Assign(
                actualValueExp, Expression.Field(
                    Expression.ArrayAccess(
                        // ReSharper disable once AssignNullToNotNullAttribute
                        Expression.Field(targetExp, typeof(HashSet<T>).GetField("m_slots", BindingFlags.NonPublic | BindingFlags.Instance)), indexVar),
                    "value")),
            Expression.Constant(true));

        var falsePart = Expression.Constant(false);

        var block = Expression.Block(
            new[] { indexVar },
            Expression.Assign(indexVar, indexExp),
            Expression.Condition(
                Expression.GreaterThanOrEqual(indexVar, Expression.Constant(0)),
                truePart,
                falsePart));

        TryGetValue = Expression.Lambda<GetValue>(block, targetExp, itemExp, actualValueExp).Compile();
    }
}

public static class Extensions
{
    public static bool TryGetValue2<T>(this HashSet<T> source, T equalValue,  out T actualValue) {
        if (source.Count > 0) {
            if (HashSetExtensions<T>.TryGetValue(source, equalValue, out actualValue)) {
                return true;
            }
        }
        actualValue = default;
        return false;
    }
}

Kiểm tra:

var x = new HashSet<int> { 1, 2, 3 };
if (x.TryGetValue2(1, out var value)) {
    Console.WriteLine(value);
}

1

SortedSet có thể sẽ có thời gian tra cứu O (log n) trong trường hợp đó, nếu sử dụng đó là một tùy chọn. Vẫn không phải là O (1), nhưng ít nhất là tốt hơn.


1

Đã sửa đổi việc triển khai câu trả lời @ mp666 để nó có thể được sử dụng cho bất kỳ loại HashSet nào và cho phép ghi đè trình so sánh bình đẳng mặc định.

public interface IRetainingComparer<T> : IEqualityComparer<T>
{
    T Key { get; }
    void ClearKeyCache();
}

/// <summary>
/// An <see cref="IEqualityComparer{T}"/> that retains the last key that successfully passed <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// This class relies on the fact that <see cref="HashSet{T}"/> calls the <see cref="IEqualityComparer{T}.Equals(T,T)"/> with the first parameter
/// being an existing element and the second parameter being the one passed to the initiating call to <see cref="HashSet{T}"/> (eg. <see cref="HashSet{T}.Contains(T)"/>).
/// </summary>
/// <typeparam name="T">The type of object being compared.</typeparam>
/// <remarks>This class is thread-safe but may should not be used with any sort of parallel access (PLINQ).</remarks>
public class RetainingEqualityComparerObject<T> : IRetainingComparer<T> where T : class
{
    private readonly IEqualityComparer<T> _comparer;

    [ThreadStatic]
    private static WeakReference<T> _retained;

    public RetainingEqualityComparerObject(IEqualityComparer<T> comparer)
    {
        _comparer = comparer;
    }

    /// <summary>
    /// The retained instance on side 'a' of the <see cref="Equals"/> call which successfully met the equality requirement agains side 'b'.
    /// </summary>
    /// <remarks>Uses a <see cref="WeakReference{T}"/> so unintended memory leaks are not encountered.</remarks>
    public T Key
    {
        get
        {
            T retained;
            return _retained == null ? null : _retained.TryGetTarget(out retained) ? retained : null;
        }
    }


    /// <summary>
    /// Sets the retained <see cref="Key"/> to the default value.
    /// </summary>
    /// <remarks>This should be called prior to performing an operation that calls <see cref="Equals"/>.</remarks>
    public void ClearKeyCache()
    {
        _retained = _retained ?? new WeakReference<T>(null);
        _retained.SetTarget(null);
    }

    /// <summary>
    /// Test two objects of type <see cref="T"/> for equality retaining the object if successful.
    /// </summary>
    /// <param name="a">An instance of <see cref="T"/>.</param>
    /// <param name="b">A second instance of <see cref="T"/> to compare against <paramref name="a"/>.</param>
    /// <returns>True if <paramref name="a"/> and <paramref name="b"/> are equal, false otherwise.</returns>
    public bool Equals(T a, T b)
    {
        if (!_comparer.Equals(a, b))
        {
            return false;
        }

        _retained = _retained ?? new WeakReference<T>(null);
        _retained.SetTarget(a);
        return true;
    }

    /// <summary>
    /// Gets the hash code value of an instance of <see cref="T"/>.
    /// </summary>
    /// <param name="o">The instance of <see cref="T"/> to obtain a hash code from.</param>
    /// <returns>The hash code value from <paramref name="o"/>.</returns>
    public int GetHashCode(T o)
    {
        return _comparer.GetHashCode(o);
    }
}

/// <summary>
/// An <see cref="IEqualityComparer{T}"/> that retains the last key that successfully passed <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// This class relies on the fact that <see cref="HashSet{T}"/> calls the <see cref="IEqualityComparer{T}.Equals(T,T)"/> with the first parameter
/// being an existing element and the second parameter being the one passed to the initiating call to <see cref="HashSet{T}"/> (eg. <see cref="HashSet{T}.Contains(T)"/>).
/// </summary>
/// <typeparam name="T">The type of object being compared.</typeparam>
/// <remarks>This class is thread-safe but may should not be used with any sort of parallel access (PLINQ).</remarks>
public class RetainingEqualityComparerStruct<T> : IRetainingComparer<T> where T : struct 
{
    private readonly IEqualityComparer<T> _comparer;

    [ThreadStatic]
    private static T _retained;

    public RetainingEqualityComparerStruct(IEqualityComparer<T> comparer)
    {
        _comparer = comparer;
    }

    /// <summary>
    /// The retained instance on side 'a' of the <see cref="Equals"/> call which successfully met the equality requirement agains side 'b'.
    /// </summary>
    public T Key => _retained;


    /// <summary>
    /// Sets the retained <see cref="Key"/> to the default value.
    /// </summary>
    /// <remarks>This should be called prior to performing an operation that calls <see cref="Equals"/>.</remarks>
    public void ClearKeyCache()
    {
        _retained = default(T);
    }

    /// <summary>
    /// Test two objects of type <see cref="T"/> for equality retaining the object if successful.
    /// </summary>
    /// <param name="a">An instance of <see cref="T"/>.</param>
    /// <param name="b">A second instance of <see cref="T"/> to compare against <paramref name="a"/>.</param>
    /// <returns>True if <paramref name="a"/> and <paramref name="b"/> are equal, false otherwise.</returns>
    public bool Equals(T a, T b)
    {
        if (!_comparer.Equals(a, b))
        {
            return false;
        }

        _retained = a;
        return true;
    }

    /// <summary>
    /// Gets the hash code value of an instance of <see cref="T"/>.
    /// </summary>
    /// <param name="o">The instance of <see cref="T"/> to obtain a hash code from.</param>
    /// <returns>The hash code value from <paramref name="o"/>.</returns>
    public int GetHashCode(T o)
    {
        return _comparer.GetHashCode(o);
    }
}

/// <summary>
/// Provides TryGetValue{T} functionality similar to that of <see cref="IDictionary{TKey,TValue}"/>'s implementation.
/// </summary>
public class ExtendedHashSet<T> : HashSet<T>
{
    /// <summary>
    /// This class is guaranteed to wrap the <see cref="IEqualityComparer{T}"/> with one of the <see cref="IRetainingComparer{T}"/>
    /// implementations so this property gives convenient access to the interfaced comparer.
    /// </summary>
    private IRetainingComparer<T> RetainingComparer => (IRetainingComparer<T>)Comparer;

    /// <summary>
    /// Creates either a <see cref="RetainingEqualityComparerStruct{T}"/> or <see cref="RetainingEqualityComparerObject{T}"/>
    /// depending on if <see cref="T"/> is a reference type or a value type.
    /// </summary>
    /// <param name="comparer">(optional) The <see cref="IEqualityComparer{T}"/> to wrap. This will be set to <see cref="EqualityComparer{T}.Default"/> if none provided.</param>
    /// <returns>An instance of <see cref="IRetainingComparer{T}"/>.</returns>
    private static IRetainingComparer<T> Create(IEqualityComparer<T> comparer = null)
    {
        return (IRetainingComparer<T>) (typeof(T).IsValueType ? 
            Activator.CreateInstance(typeof(RetainingEqualityComparerStruct<>)
                .MakeGenericType(typeof(T)), comparer ?? EqualityComparer<T>.Default)
            :
            Activator.CreateInstance(typeof(RetainingEqualityComparerObject<>)
                .MakeGenericType(typeof(T)), comparer ?? EqualityComparer<T>.Default));
    }

    public ExtendedHashSet() : base(Create())
    {
    }

    public ExtendedHashSet(IEqualityComparer<T> comparer) : base(Create(comparer))
    {
    }

    public ExtendedHashSet(IEnumerable<T> collection) : base(collection, Create())
    {
    }

    public ExtendedHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) : base(collection, Create(comparer))
    {
    }

    /// <summary>
    /// Attempts to find a key in the <see cref="HashSet{T}"/> and, if found, places the instance in <paramref name="original"/>.
    /// </summary>
    /// <param name="value">The key used to search the <see cref="HashSet{T}"/>.</param>
    /// <param name="original">
    /// The matched instance from the <see cref="HashSet{T}"/> which is not neccessarily the same as <paramref name="value"/>.
    /// This will be set to null for reference types or default(T) for value types when no match found.
    /// </param>
    /// <returns>True if a key in the <see cref="HashSet{T}"/> matched <paramref name="value"/>, False if no match found.</returns>
    public bool TryGetValue(T value, out T original)
    {
        var comparer = RetainingComparer;
        comparer.ClearKeyCache();

        if (Contains(value))
        {
            original = comparer.Key;
            return true;
        }

        original = default(T);
        return false;
    }
}

public static class HashSetExtensions
{
    /// <summary>
    /// Attempts to find a key in the <see cref="HashSet{T}"/> and, if found, places the instance in <paramref name="original"/>.
    /// </summary>
    /// <param name="hashSet">The instance of <see cref="HashSet{T}"/> extended.</param>
    /// <param name="value">The key used to search the <see cref="HashSet{T}"/>.</param>
    /// <param name="original">
    /// The matched instance from the <see cref="HashSet{T}"/> which is not neccessarily the same as <paramref name="value"/>.
    /// This will be set to null for reference types or default(T) for value types when no match found.
    /// </param>
    /// <returns>True if a key in the <see cref="HashSet{T}"/> matched <paramref name="value"/>, False if no match found.</returns>
    /// <exception cref="ArgumentNullException">If <paramref name="hashSet"/> is null.</exception>
    /// <exception cref="ArgumentException">
    /// If <paramref name="hashSet"/> does not have a <see cref="HashSet{T}.Comparer"/> of type <see cref="IRetainingComparer{T}"/>.
    /// </exception>
    public static bool TryGetValue<T>(this HashSet<T> hashSet, T value, out T original)
    {
        if (hashSet == null)
        {
            throw new ArgumentNullException(nameof(hashSet));
        }

        if (hashSet.Comparer.GetType().IsInstanceOfType(typeof(IRetainingComparer<T>)))
        {
            throw new ArgumentException($"HashSet must have an equality comparer of type '{nameof(IRetainingComparer<T>)}' to use this functionality", nameof(hashSet));
        }

        var comparer = (IRetainingComparer<T>)hashSet.Comparer;
        comparer.ClearKeyCache();

        if (hashSet.Contains(value))
        {
            original = comparer.Key;
            return true;
        }

        original = default(T);
        return false;
    }
}

1
Vì bạn đang sử dụng phương thức mở rộng Linq Enumerable.Contains, nó sẽ liệt kê tất cả các phần tử của tập hợp và so sánh chúng, làm mất đi bất kỳ lợi ích nào mà việc triển khai băm của tập hợp mang lại. Sau đó, bạn cũng có thể chỉ cần viết set.SingleOrDefault(e => set.Comparer.Equals(e, obj)), có cùng hành vi và đặc điểm hiệu suất như giải pháp của bạn.
Daniel AA Pelsmaeker,

@Virtlink Bắt tốt - Bạn hoàn toàn đúng. Tôi sẽ sửa đổi câu trả lời của mình.
Graeme Wicksted

Tuy nhiên, nếu bạn bọc một HashSet sử dụng bộ so sánh của bạn bên trong, nó sẽ hoạt động. Như thế này: Utillib / ExtHashSet
Daniel AA Pelsmaeker

@Virtlink cảm ơn bạn! Tôi đã kết thúc việc gói HashSet như một tùy chọn nhưng cung cấp các trình so sánh và một phương thức mở rộng để có thêm tính linh hoạt. Bây giờ nó an toàn theo luồng và sẽ không bị rò rỉ bộ nhớ ... nhưng nó nhiều mã hơn tôi mong đợi!
Graeme Wicksted

@Francois Viết đoạn mã trên là một bài tập tìm ra giải pháp thời gian / bộ nhớ "tối ưu"; tuy nhiên, tôi không khuyên bạn nên sử dụng phương pháp này. Sử dụng Từ điển <T, T> với IEqualityComparer tùy chỉnh sẽ dễ hiểu hơn nhiều và có tính tương lai!
Graeme Wicksted

-2

HashSet có phương thức Chứa (T) .

Bạn có thể chỉ định IEqualityComparer nếu bạn cần một phương pháp so sánh tùy chỉnh (ví dụ: lưu trữ một đối tượng người, nhưng sử dụng SSN để so sánh bình đẳng).


-11

Bạn cũng có thể sử dụng phương thức ToList () và áp dụng một trình chỉ mục cho điều đó.

HashSet<string> mySet = new HashSet();
mySet.Add("mykey");
string key = mySet.toList()[0];

Tôi không chắc tại sao bạn lại nhận được phiếu bầu khi tôi áp dụng logic này, nó hoạt động. Tôi cần trích xuất các giá trị từ một cấu trúc bắt đầu bằng Dictionary <string, ISet <String>> trong đó ISet chứa x số giá trị. Cách trực tiếp nhất để lấy các giá trị đó là lặp qua từ điển kéo khóa và Giá trị ISet. Sau đó, tôi lặp qua ISet để hiển thị các giá trị riêng lẻ. Nó không phải là thanh lịch, nhưng nó đã hoạt động.
'19
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.