feature/edit/subset (#283)

* Changing query/subset API to only use Result on success, Error on error

* Creating an interservice API for getting query result subsets

* Updates to subset API

* RowStartIndex is now long
* Output of query/subset is a 2D array of DbCellValue
* Adding LongSkip method to LongList to allow skipping ahead by longs
* Moving LongList back to ServiceLayer utilities. Move refactoring

* Stubbing out request for edit/subset

* Initial implementation of getting edit rows

* Unit tests for RowEdit and RowDelete .GetEditRow

* Fixing major bugs in LongList implementation, adding much more thorough tests

* Adding some more unit tests and fixes to make unit tests pass

* Fixing comment
This commit is contained in:
Benjamin Russell
2017-03-21 15:14:04 -07:00
committed by GitHub
parent 9e576dea92
commit d7ecfb1a87
26 changed files with 1165 additions and 165 deletions

View File

@@ -0,0 +1,45 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts
{
/// <summary>
/// A way to return a row in a result set that is being edited. It contains state about whether
/// or not the row is dirty
/// </summary>
public class EditRow
{
public enum EditRowState
{
Clean = 0,
DirtyInsert = 1,
DirtyDelete = 2,
DirtyUpdate = 3
}
/// <summary>
/// The cells in the row. If the row has pending changes, they will be represented in
/// this list
/// </summary>
public DbCellValue[] Cells { get; set; }
/// <summary>
/// Internal ID of the row. This should be used whenever referencing a row in row edit operations.
/// </summary>
public long Id { get; set; }
/// <summary>
/// Whether or not the row has changes pending
/// </summary>
public bool IsDirty => State != EditRowState.Clean;
/// <summary>
/// What type of dirty state (or lack thereof) the row is
/// </summary>
public EditRowState State { get; set; }
}
}

View File

@@ -0,0 +1,52 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts
{
/// <summary>
/// Parameters for a subset retrieval request
/// </summary>
public class EditSubsetParams : SessionOperationParams
{
/// <summary>
/// Beginning index of the rows to return from the selected resultset. This index will be
/// included in the results.
/// </summary>
public long RowStartIndex { get; set; }
/// <summary>
/// Number of rows to include in the result of this request. If the number of the rows
/// exceeds the number of rows available after the start index, all available rows after
/// the start index will be returned.
/// </summary>
public int RowCount { get; set; }
}
/// <summary>
/// Parameters for the result of a subset retrieval request
/// </summary>
public class EditSubsetResult
{
/// <summary>
/// The number of rows returned from result set, useful for determining if less rows were
/// returned than requested.
/// </summary>
public int RowCount { get; set; }
/// <summary>
/// The requested subset of rows, with information about whether or not the rows are dirty
/// </summary>
public EditRow[] Subset { get; set; }
}
public class EditSubsetRequest
{
public static readonly
RequestType<EditSubsetParams, EditSubsetResult> Type =
RequestType<EditSubsetParams, EditSubsetResult>.Create("edit/subset");
}
}

View File

@@ -91,6 +91,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
serviceHost.SetRequestHandler(EditInitializeRequest.Type, HandleInitializeRequest);
serviceHost.SetRequestHandler(EditRevertCellRequest.Type, HandleRevertCellRequest);
serviceHost.SetRequestHandler(EditRevertRowRequest.Type, HandleRevertRowRequest);
serviceHost.SetRequestHandler(EditSubsetRequest.Type, HandleSubsetRequest);
serviceHost.SetRequestHandler(EditUpdateCellRequest.Type, HandleUpdateCellRequest);
}
@@ -241,6 +242,20 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
});
}
internal Task HandleSubsetRequest(EditSubsetParams subsetParams,
RequestContext<EditSubsetResult> requestContext)
{
return HandleSessionRequest(subsetParams, requestContext, session =>
{
EditRow[] rows = session.GetRows(subsetParams.RowStartIndex, subsetParams.RowCount).Result;
return new EditSubsetResult
{
RowCount = rows.Length,
Subset = rows
};
});
}
internal Task HandleUpdateCellRequest(EditUpdateCellParams updateParams,
RequestContext<EditUpdateCellResult> requestContext)
{

View File

@@ -13,6 +13,7 @@ using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.EditData
@@ -186,6 +187,60 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
}
}
/// <summary>
/// Retrieves a subset of rows with the pending updates applied. If more rows than exist
/// are requested, only the rows that exist will be returned.
/// </summary>
/// <param name="startIndex">Index to start returning rows from</param>
/// <param name="rowCount">The number of rows to return.</param>
/// <returns>An array of rows with pending edits applied</returns>
public async Task<EditRow[]> GetRows(long startIndex, int rowCount)
{
// Get the cached rows from the result set
ResultSetSubset cachedRows = startIndex < associatedResultSet.RowCount
? await associatedResultSet.GetSubset(startIndex, rowCount)
: new ResultSetSubset
{
RowCount = 0,
Rows = new DbCellValue[][] { }
};
// Convert the rows into EditRows and apply the changes we have
List<EditRow> editRows = new List<EditRow>();
for (int i = 0; i < cachedRows.RowCount; i++)
{
long rowId = i + startIndex;
RowEditBase edr;
if (EditCache.TryGetValue(rowId, out edr))
{
// Ask the edit object to generate an edit row
editRows.Add(edr.GetEditRow(cachedRows.Rows[i]));
}
else
{
// Package up the existing row into a clean edit row
EditRow er = new EditRow
{
Id = rowId,
Cells = cachedRows.Rows[i],
State = EditRow.EditRowState.Clean
};
editRows.Add(er);
}
}
// If the requested range of rows was at the end of the original cell set and we have
// added new rows, we need to reflect those changes
if (rowCount > cachedRows.RowCount)
{
long endIndex = startIndex + cachedRows.RowCount;
var newRows = EditCache.Where(edit => edit.Key >= endIndex).Take(rowCount - cachedRows.RowCount);
editRows.AddRange(newRows.Select(newRow => newRow.Value.GetEditRow(null)));
}
return editRows.ToArray();
}
/// <summary>
/// Reverts a cell in a pending edit
/// </summary>

View File

@@ -85,6 +85,22 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
#region Properties
/// <summary>
/// Converts the cell update to a DbCellValue
/// </summary>
public DbCellValue AsDbCellValue
{
get
{
return new DbCellValue
{
DisplayValue = ValueAsString,
IsNull = Value == DBNull.Value,
RawObject = Value
};
}
}
/// <summary>
/// The column that the cell will be placed in
/// </summary>

View File

@@ -122,6 +122,26 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
return command;
}
/// <summary>
/// Generates a edit row that represents a row pending insertion
/// </summary>
/// <param name="cachedRow">Original, cached cell contents. (Should be null in this case)</param>
/// <returns>EditRow of pending update</returns>
public override EditRow GetEditRow(DbCellValue[] cachedRow)
{
// Iterate over the new cells. If they are null, generate a blank value
DbCellValue[] editCells = newCells.Select(cell => cell == null
? new DbCellValue {DisplayValue = string.Empty, IsNull = false, RawObject = null}
: cell.AsDbCellValue)
.ToArray();
return new EditRow
{
Id = RowId,
Cells = editCells,
State = EditRow.EditRowState.DirtyInsert
};
}
/// <summary>
/// Generates the INSERT INTO statement that will apply the row creation
/// </summary>

View File

@@ -9,6 +9,7 @@ using System.Globalization;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
@@ -72,6 +73,24 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
return command;
}
/// <summary>
/// Generates a edit row that represents a row pending deletion. All the original cells are
/// intact but the state is dirty.
/// </summary>
/// <param name="cachedRow">Original, cached cell contents</param>
/// <returns>EditRow that is pending deletion</returns>
public override EditRow GetEditRow(DbCellValue[] cachedRow)
{
Validate.IsNotNull(nameof(cachedRow), cachedRow);
return new EditRow
{
Id = RowId,
Cells = cachedRow,
State = EditRow.EditRowState.DirtyDelete
};
}
/// <summary>
/// Generates a DELETE statement to delete this row
/// </summary>

View File

@@ -85,6 +85,14 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// <returns>Command to commit the change to the db</returns>
public abstract DbCommand GetCommand(DbConnection connection);
/// <summary>
/// Generates a row that has the pending update applied. The dirty status of the row is
/// reflected in the returned EditRow.
/// </summary>
/// <param name="cachedRow">The original, cached row values</param>
/// <returns>An EditRow with the pending changes applied</returns>
public abstract EditRow GetEditRow(DbCellValue[] cachedRow);
/// <summary>
/// Converts the row edit into a SQL statement
/// </summary>

View File

@@ -113,6 +113,30 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
return command;
}
/// <summary>
/// Generates a edit row that represents a row with pending update. The cells pending
/// updates are merged into the unchanged cells.
/// </summary>
/// <param name="cachedRow">Original, cached cell contents</param>
/// <returns>EditRow with pending updates</returns>
public override EditRow GetEditRow(DbCellValue[] cachedRow)
{
Validate.IsNotNull(nameof(cachedRow), cachedRow);
// For each cell that is pending update, replace the db cell value with a new one
foreach (var cellUpdate in cellUpdates)
{
cachedRow[cellUpdate.Key] = cellUpdate.Value.AsDbCellValue;
}
return new EditRow
{
Id = RowId,
Cells = cachedRow,
State = EditRow.EditRowState.DirtyUpdate
};
}
/// <summary>
/// Constructs an update statement to change the associated row.
/// </summary>

View File

@@ -355,7 +355,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <param name="startRow">The starting row of the results</param>
/// <param name="rowCount">How many rows to retrieve</param>
/// <returns>A subset of results</returns>
public Task<ResultSetSubset> GetSubset(int resultSetIndex, int startRow, int rowCount)
public Task<ResultSetSubset> GetSubset(int resultSetIndex, long startRow, int rowCount)
{
ResultSet targetResultSet;
lock (resultSets)

View File

@@ -19,6 +19,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// <summary>
/// 2D array of the cell values requested from result set
/// </summary>
public string[][] Rows { get; set; }
public DbCellValue[][] Rows { get; set; }
}
}

View File

@@ -31,7 +31,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// Beginning index of the rows to return from the selected resultset. This index will be
/// included in the results.
/// </summary>
public int RowsStartIndex { get; set; }
public long RowsStartIndex { get; set; }
/// <summary>
/// Number of rows to include in the result of this request. If the number of the rows
@@ -46,11 +46,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
/// </summary>
public class SubsetResult
{
/// <summary>
/// Subset request error messages. Optional, can be set to null to indicate no errors
/// </summary>
public string Message { get; set; }
/// <summary>
/// The requested subset of results. Optional, can be set to null to indicate an error
/// </summary>

View File

@@ -280,7 +280,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <param name="startRow">The starting row of the results</param>
/// <param name="rowCount">How many rows to retrieve</param>
/// <returns>A subset of results</returns>
public Task<ResultSetSubset> GetSubset(int batchIndex, int resultSetIndex, int startRow, int rowCount)
public Task<ResultSetSubset> GetSubset(int batchIndex, int resultSetIndex, long startRow, int rowCount)
{
// Sanity check to make sure that the batch is within bounds
if (batchIndex < 0 || batchIndex >= Batches.Length)

View File

@@ -176,42 +176,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
{
try
{
// Attempt to load the query
Query query;
if (!ActiveQueries.TryGetValue(subsetParams.OwnerUri, out query))
{
await requestContext.SendResult(new SubsetResult
{
Message = SR.QueryServiceRequestsNoQuery
});
return;
}
// Retrieve the requested subset and return it
ResultSetSubset subset = await InterServiceResultSubset(subsetParams);
var result = new SubsetResult
{
Message = null,
ResultSubset = await query.GetSubset(subsetParams.BatchIndex,
subsetParams.ResultSetIndex, subsetParams.RowsStartIndex, subsetParams.RowsCount)
ResultSubset = subset
};
await requestContext.SendResult(result);
}
catch (InvalidOperationException ioe)
{
// Return the error as a result
await requestContext.SendResult(new SubsetResult
{
Message = ioe.Message
});
}
catch (ArgumentOutOfRangeException aoore)
{
// Return the error as a result
await requestContext.SendResult(new SubsetResult
{
Message = aoore.Message
});
}
catch (Exception e)
{
// This was unexpected, so send back as error
@@ -415,7 +386,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <param name="ownerUri">The identifier of the query to be disposed</param>
/// <param name="successAction">Action to perform on success</param>
/// <param name="failureAction">Action to perform on failure</param>
/// <returns></returns>
public async Task InterServiceDisposeQuery(string ownerUri, Func<Task> successAction,
Func<string, Task> failureAction)
{
@@ -444,6 +414,29 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
}
}
/// <summary>
/// Retrieves the requested subset of rows from the requested result set. Intended to be
/// called by another service.
/// </summary>
/// <param name="subsetParams">Parameters for the subset to retrieve</param>
/// <returns>The requested subset</returns>
/// <exception cref="ArgumentOutOfRangeException">The requested query does not exist</exception>
public async Task<ResultSetSubset> InterServiceResultSubset(SubsetParams subsetParams)
{
Validate.IsNotNullOrEmptyString(nameof(subsetParams.OwnerUri), subsetParams.OwnerUri);
// Attempt to load the query
Query query;
if (!ActiveQueries.TryGetValue(subsetParams.OwnerUri, out query))
{
throw new ArgumentOutOfRangeException(SR.QueryServiceRequestsNoQuery);
}
// Retrieve the requested subset and return it
return await query.GetSubset(subsetParams.BatchIndex, subsetParams.ResultSetIndex,
subsetParams.RowsStartIndex, subsetParams.RowsCount);
}
#endregion
#region Private Helpers

View File

@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
@@ -223,7 +224,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <param name="startRow">The starting row of the results</param>
/// <param name="rowCount">How many rows to retrieve</param>
/// <returns>A subset of results</returns>
public Task<ResultSetSubset> GetSubset(int startRow, int rowCount)
public Task<ResultSetSubset> GetSubset(long startRow, int rowCount)
{
// Sanity check to make sure that the results have been read beforehand
if (!hasBeenRead)
@@ -244,7 +245,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
return Task.Factory.StartNew(() =>
{
string[][] rows;
DbCellValue[][] rows;
using (IFileStreamReader fileStreamReader = fileStreamFactory.GetReader(outputFileName))
{
@@ -255,19 +256,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// Iterate over all the rows and process them into a list of string builders
// ReSharper disable once AccessToDisposedClosure The lambda is used immediately in string.Join call
IEnumerable<string> rowValues = fileOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)[0].DisplayValue);
rows = new[] { new[] { string.Join(string.Empty, rowValues) } };
string singleString = string.Join(string.Empty, rowValues);
DbCellValue cellValue = new DbCellValue
{
DisplayValue = singleString,
IsNull = false,
RawObject = singleString
};
rows = new[] { new[] { cellValue } };
}
else
{
// Figure out which rows we need to read back
IEnumerable<long> rowOffsets = fileOffsets.Skip(startRow).Take(rowCount);
IEnumerable<long> rowOffsets = fileOffsets.LongSkip(startRow).Take(rowCount);
// Iterate over the rows we need and process them into output
// ReSharper disable once AccessToDisposedClosure The lambda is used immediately in .ToArray call
rows = rowOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)
.Select(cell => cell.DisplayValue).ToArray())
.ToArray();
rows = rowOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns).ToArray()).ToArray();
}
}
// Retrieve the subset of the results as per the request

View File

@@ -0,0 +1,336 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Collection class that permits storage of over <c>int.MaxValue</c> items. This is performed
/// by using a 2D list of lists. The internal lists are only initialized as necessary. This
/// collection implements IEnumerable to make it easier to run LINQ queries against it.
/// </summary>
/// <remarks>
/// This class is based on code from $\Data Tools\SSMS_Main\sql\ssms\core\DataStorage\ArrayList64.cs
/// with additions to bring it up to .NET 4.5 standards
/// </remarks>
/// <typeparam name="T">Type of the values to store</typeparam>
public class LongList<T> : IEnumerable<T>
{
#region Member Variables
private List<List<T>> expandedList;
private readonly List<T> shortList;
#endregion
/// <summary>
/// Creates a new long list
/// </summary>
public LongList()
{
shortList = new List<T>();
ExpandListSize = int.MaxValue;
Count = 0;
}
#region Properties
/// <summary>
/// The total number of elements in the array
/// </summary>
public long Count { get; private set; }
/// <summary>
/// Used to get or set the value at a given index in the list
/// </summary>
/// <param name="index">Index into the list to access</param>
public T this[long index]
{
get
{
return GetItem(index);
}
set
{
SetItem(index, value);
}
}
/// <summary>
/// The number of elements to store in a single list before expanding into multiple lists
/// </summary>
public int ExpandListSize { get; internal set; }
#endregion
#region Public Methods
/// <summary>
/// Adds the specified value to the end of the list
/// </summary>
/// <param name="val">Value to add to the list</param>
/// <returns>Index of the item that was just added</returns>
public long Add(T val)
{
if (Count < this.ExpandListSize)
{
shortList.Add(val);
}
else // need to split values into several arrays
{
if (expandedList == null)
{
// very inefficient so delay as much as possible
// immediately add 0th array
expandedList = new List<List<T>> {shortList};
}
int arrayIndex = (int)(Count / this.ExpandListSize); // 0 based
List<T> arr;
if (expandedList.Count <= arrayIndex) // need to make a new array
{
arr = new List<T>();
expandedList.Add(arr);
}
else // use existing array
{
arr = expandedList[arrayIndex];
}
arr.Add(val);
}
return (++Count);
}
/// <summary>
/// Returns the item at the specified index
/// </summary>
/// <param name="index">Index of the item to return</param>
/// <returns>The item at the index specified</returns>
public T GetItem(long index)
{
Validate.IsWithinRange(nameof(index), index, 0, Count - 1);
T val = default(T);
if (Count < this.ExpandListSize)
{
int i32Index = Convert.ToInt32(index);
val = shortList[i32Index];
}
else
{
int iArray32Index = (int) (index / this.ExpandListSize);
if (expandedList.Count > iArray32Index)
{
List<T> arr = expandedList[iArray32Index];
int i32Index = (int) (index % this.ExpandListSize);
if (arr.Count > i32Index)
{
val = arr[i32Index];
}
}
}
return val;
}
/// <summary>
/// Skips ahead the number of elements requested and returns the elements after that many elements
/// </summary>
/// <param name="start">The number of elements to skip</param>
/// <returns>All elements after the number of elements to skip</returns>
public IEnumerable<T> LongSkip(long start)
{
Validate.IsWithinRange(nameof(start), start, 0, Count - 1);
// Generate an enumerator over this list and jump ahead to the position we want
LongListEnumerator<T> longEnumerator = new LongListEnumerator<T>(this) {Index = start - 1};
// While there are results to get, yield return them
while (longEnumerator.MoveNext())
{
yield return longEnumerator.Current;
}
}
/// <summary>
/// Sets the item at the specified index
/// </summary>
/// <param name="index">Index of the item to set</param>
/// <param name="value">The item to store at the index specified</param>
public void SetItem(long index, T value)
{
Validate.IsLessThan(nameof(index), index, Count);
if (Count <= this.ExpandListSize)
{
int i32Index = Convert.ToInt32(index);
shortList[i32Index] = value;
}
else
{
int iArray32Index = (int) (index / this.ExpandListSize);
List<T> arr = expandedList[iArray32Index];
int i32Index = (int)(index % this.ExpandListSize);
arr[i32Index] = value;
}
}
/// <summary>
/// Removes an item at the specified location and shifts all the items after the provided
/// index up by one.
/// </summary>
/// <param name="index">The index to remove from the list</param>
public void RemoveAt(long index)
{
Validate.IsWithinRange(nameof(index), index, 0, Count - 1);
if (Count <= this.ExpandListSize)
{
int iArray32MemberIndex = Convert.ToInt32(index); // 0 based
shortList.RemoveAt(iArray32MemberIndex);
}
else // handle the case of multiple arrays
{
// find out which array it is in
int arrayIndex = (int) (index / this.ExpandListSize);
List<T> arr = expandedList[arrayIndex];
// find out index into this array
int iArray32MemberIndex = (int) (index % this.ExpandListSize);
arr.RemoveAt(iArray32MemberIndex);
// now shift members of the array back one
//int iArray32TotalIndex = (int) (Count / this.ExpandListSize);
for (int i = arrayIndex + 1; i < expandedList.Count; i++)
{
List<T> arr1 = expandedList[i - 1];
List<T> arr2 = expandedList[i];
arr1.Add(arr2[0]);
arr2.RemoveAt(0);
if (arr2.Count == 0)
{
expandedList.RemoveAt(i);
}
}
}
--Count;
}
#endregion
#region IEnumerable<object> Implementation
/// <summary>
/// Returns a generic enumerator for enumeration of this LongList
/// </summary>
/// <returns>Enumerator for LongList</returns>
public IEnumerator<T> GetEnumerator()
{
return new LongListEnumerator<T>(this);
}
/// <summary>
/// Returns an enumerator for enumeration of this LongList
/// </summary>
/// <returns></returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
public class LongListEnumerator<TEt> : IEnumerator<TEt>
{
/// <summary>
/// The current list that we're iterating over.
/// </summary>
private readonly LongList<TEt> localList;
/// <summary>
/// The current index into the list
/// </summary>
private long index;
/// <summary>
/// Constructs a new enumerator for a given LongList
/// </summary>
/// <param name="list">The list to enumerate</param>
public LongListEnumerator(LongList<TEt> list)
{
localList = list;
index = -1;
Current = default(TEt);
}
/// <summary>
/// The index into the list of the item that is the current item. Upon setting,
/// <see cref="Current"/> will be updated if the index is in range. Otherwise,
/// <c>default(<see cref="TEt"/>)</c> will be used.
/// </summary>
public long Index
{
get { return index; }
set
{
index = value;
Current = value >= localList.Count || value < 0
? default(TEt)
: localList[index];
}
}
#region IEnumerator Implementation
/// <summary>
/// Returns the current item in the enumeration
/// </summary>
public TEt Current { get; private set; }
/// <summary>
/// Returns the current item in the enumeration
/// </summary>
object IEnumerator.Current => Current;
/// <summary>
/// Moves to the next item in the list we're iterating over
/// </summary>
/// <returns>Whether or not the move was successful</returns>
public bool MoveNext()
{
Index++;
return Index < localList.Count;
}
/// <summary>
/// Resets the enumeration
/// </summary>
public void Reset()
{
Index = 0;
Current = default(TEt);
}
/// <summary>
/// Disposal method. Does nothing.
/// </summary>
public void Dispose()
{
}
#endregion
}
}
}