1 //------------------------------------------------------------------------------
2 // <copyright file="DbReferenceCollection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System
.Data
.ProviderBase
{
12 using System
.Collections
;
13 using System
.Collections
.Generic
;
14 using System
.Diagnostics
;
15 using System
.Threading
;
17 internal abstract class DbReferenceCollection
{
19 private struct CollectionEntry
{
20 private int _tag
; // information about the reference
21 private WeakReference _weak
; // the reference itself.
23 public void NewTarget(int tag
, object target
) {
24 Debug
.Assert(!HasTarget
, "Entry already has a valid target");
25 Debug
.Assert(tag
!= 0, "Bad tag");
26 Debug
.Assert(target
!= null, "Invalid target");
29 _weak
= new WeakReference(target
, false);
32 _weak
.Target
= target
;
37 public void RemoveTarget() {
41 public bool HasTarget
{
43 return ((_tag
!= 0) && (_weak
.IsAlive
));
53 public object Target
{
55 return (_tag
== 0 ? null : _weak
.Target
);
60 private const int LockPollTime
= 100; // Time to wait (in ms) between attempting to get the _itemLock
61 private const int DefaultCollectionSize
= 20; // Default size for the collection, and the amount to grow everytime the collection is full
62 private CollectionEntry
[] _items
; // The collection of items we are keeping track of
63 private readonly object _itemLock
; // Used to synchronize access to the _items collection
64 private int _optimisticCount
; // (#ItemsAdded - #ItemsRemoved) - This estimates the number of items that we *should* have (but doesn't take into account item targets being GC'd)
65 private int _lastItemIndex
; // Location of the last item in _items
66 private volatile bool _isNotifying
; // Indicates that the collection is currently being notified (and, therefore, about to be cleared)
68 protected DbReferenceCollection() {
69 _items
= new CollectionEntry
[DefaultCollectionSize
];
70 _itemLock
= new object();
75 abstract public void Add(object value, int tag
);
77 protected void AddItem(object value, int tag
) {
78 Debug
.Assert(null != value && 0 != tag
, "AddItem with null value or 0 tag");
79 bool itemAdded
= false;
82 // Try to find a free spot
83 for (int i
= 0; i
<= _lastItemIndex
; ++i
) {
84 if (_items
[i
].Tag
== 0) {
85 _items
[i
].NewTarget(tag
, value);
86 Debug
.Assert(_items
[i
].HasTarget
, "missing expected target");
92 // No free spots, can we just add on to the end?
93 if ((!itemAdded
) && (_lastItemIndex
+ 1 < _items
.Length
)) {
95 _items
[_lastItemIndex
].NewTarget(tag
, value);
99 // If no free spots and no space at the end, try to find a dead item
101 for (int i
= 0; i
<= _lastItemIndex
; ++i
) {
102 if (!_items
[i
].HasTarget
) {
103 _items
[i
].NewTarget(tag
, value);
104 Debug
.Assert(_items
[i
].HasTarget
, "missing expected target");
111 // If nothing was free, then resize and add to the end
113 Array
.Resize
<CollectionEntry
>(ref _items
, _items
.Length
* 2);
115 _items
[_lastItemIndex
].NewTarget(tag
, value);
122 internal T FindItem
<T
>(int tag
, Func
<T
, bool> filterMethod
) where T
: class {
123 bool lockObtained
= false;
125 TryEnterItemLock(ref lockObtained
);
127 if (_optimisticCount
> 0) {
128 // Loop through the items
129 for (int counter
= 0; counter
<= _lastItemIndex
; counter
++) {
130 // Check tag (should be easiest and quickest)
131 if (_items
[counter
].Tag
== tag
) {
132 // NOTE: Check if the returned value is null twice may seem wasteful, but this if for performance
133 // Since checking for null twice is cheaper than calling both HasTarget and Target OR always attempting to typecast
134 object value = _items
[counter
].Target
;
136 // Make sure the item has the correct type and passes the filtering
137 T tempItem
= value as T
;
138 if ((tempItem
!= null) && (filterMethod(tempItem
))) {
148 ExitItemLockIfNeeded(lockObtained
);
151 // If we got to here, then no item was found, so return null
155 public void Notify(int message
) {
156 bool lockObtained
= false;
158 TryEnterItemLock(ref lockObtained
);
163 // Loop through each live item and notify it
164 if (_optimisticCount
> 0) {
165 for (int index
= 0; index
<= _lastItemIndex
; ++index
) {
166 object value = _items
[index
].Target
; // checks tag & gets target
168 NotifyItem(message
, _items
[index
].Tag
, value);
169 _items
[index
].RemoveTarget();
171 Debug
.Assert(!_items
[index
].HasTarget
, "Unexpected target after notifying");
173 _optimisticCount
= 0;
176 // Shrink collection (if needed)
177 if (_items
.Length
> 100) {
179 _items
= new CollectionEntry
[DefaultCollectionSize
];
183 _isNotifying
= false;
188 ExitItemLockIfNeeded(lockObtained
);
192 abstract protected void NotifyItem(int message
, int tag
, object value);
194 abstract public void Remove(object value);
196 protected void RemoveItem(object value) {
197 Debug
.Assert(null != value, "RemoveItem with null");
199 bool lockObtained
= false;
201 TryEnterItemLock(ref lockObtained
);
204 // Find the value, and then remove the target from our collection
205 if (_optimisticCount
> 0) {
206 for (int index
= 0; index
<= _lastItemIndex
; ++index
) {
207 if (value == _items
[index
].Target
) { // checks tag & gets target
208 _items
[index
].RemoveTarget();
217 ExitItemLockIfNeeded(lockObtained
);
221 // This is polling lock that will abandon getting the lock if _isNotifying is set to true
222 private void TryEnterItemLock(ref bool lockObtained
) {
223 // Assume that we couldn't take the lock
224 lockObtained
= false;
225 // Keep trying to take the lock until either we've taken it, or the collection is being notified
226 while ((!_isNotifying
) && (!lockObtained
)) {
227 Monitor
.TryEnter(_itemLock
, LockPollTime
, ref lockObtained
);
231 private void ExitItemLockIfNeeded(bool lockObtained
) {
233 Monitor
.Exit(_itemLock
);