1 //---------------------------------------------------------------------
2 // <copyright file="EnumerableValidator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System
.Data
.Common
.CommandTrees
.ExpressionBuilder
.Internal
12 using System
.Collections
.Generic
;
13 using System
.Data
.Common
.Utils
;
14 using System
.Diagnostics
;
17 /// Validates an input enumerable argument with a specific element type,
18 /// converting each input element into an instance of a specific output element type,
19 /// then producing a final result of another specific type.
21 /// <typeparam name="TElementIn">The element type of the input enumerable</typeparam>
22 /// <typeparam name="TElementOut">The element type that input elements are converted to</typeparam>
23 /// <typeparam name="TResult">The type of the final result</typeparam>
24 internal sealed class EnumerableValidator
<TElementIn
, TElementOut
, TResult
>
26 private readonly string argumentName
;
27 private readonly IEnumerable
<TElementIn
> target
;
29 internal EnumerableValidator(IEnumerable
<TElementIn
> argument
, string argumentName
)
31 this.argumentName
= argumentName
;
32 this.target
= argument
;
35 private bool allowEmpty
;
36 private int expectedElementCount
= -1;
37 private Func
<TElementIn
, int, TElementOut
> map
;
38 private Func
<List
<TElementOut
>, TResult
> collect
;
39 private Func
<TElementIn
, int, string> deriveName
;
42 /// Gets or sets a value that determines whether an exception is thrown if the enumerable argument is empty.
45 /// AllowEmpty is ignored if <see cref="ExpectedElementCount"/> is set.
46 /// If ExpectedElementCount is set to zero, an empty collection will not cause an exception to be thrown,
47 /// even if AllowEmpty is set to <c>false</c>.
49 public bool AllowEmpty { get { return this.allowEmpty; }
set { this.allowEmpty = value; }
}
52 /// Gets or set a value that determines the number of elements expected in the enumerable argument.
53 /// A value of <c>-1</c> indicates that any number of elements is permitted, including zero.
54 /// Use <see cref="AllowEmpty"/> to disallow an empty list when ExpectedElementCount is set to -1.
56 public int ExpectedElementCount { get { return this.expectedElementCount; }
set { this.expectedElementCount = value; }
}
59 /// Gets or sets the function used to convert an element from the enumerable argument into an instance of
60 /// the desired output element type. The position of the input element is also specified as an argument to this function.
62 public Func
<TElementIn
, int, TElementOut
> ConvertElement { get { return this.map; }
set { this.map = value; }
}
65 /// Gets or sets the function used to create the output collection from a list of converted enumerable elements.
67 public Func
<List
<TElementOut
>, TResult
> CreateResult { get { return this.collect; }
set { this.collect = value; }
}
70 /// Gets or sets an optional function that can retrieve the name of an element from the enumerable argument.
71 /// If this function is set, duplicate input element names will result in an exception. Null or empty names will
72 /// not result in an exception. If specified, this function will be called after <see cref="ConvertElement"/>.
74 public Func
<TElementIn
, int, string> GetName { get { return this.deriveName; }
set { this.deriveName = value; }
}
77 /// Validates the input enumerable, converting each input element and producing the final instance of <typeparamref name="TResult"/> as a result.
79 /// <returns>The instance of <typeparamref name="TResult"/> produced by calling the <see cref="CreateResult"/> function
80 /// on the list of elements produced by calling the <see cref="ConvertElement"/> function on each element of the input enumerable.</returns>
81 /// <exception cref="ArgumentNullException">If the input enumerable itself is null</exception>
82 /// <exception cref="ArgumentNullException">If <typeparamref name="TElementIn"/> is a nullable type and any element of the input enumerable is null.</exception>
83 /// <exception cref="ArgumentException">If <see cref="ExpectedElementCount"/> is set and the actual number of input elements is not equal to this value.</exception>
84 /// <exception cref="ArgumentException">If <see cref="ExpectedElementCount"/> is -1, <see cref="AllowEmpty"/> is set to <c>false</c> and the input enumerable is empty.</exception>
85 /// <exception cref="ArgumentException">If <see cref="GetName"/> is set and a duplicate name is derived for more than one input element.</exception>
86 /// <remarks>Other exceptions may be thrown by the <see cref="ConvertElement"/> and <see cref="CreateResult"/> functions, and by the <see cref="GetName"/> function, if specified.</remarks>
87 internal TResult
Validate()
89 return EnumerableValidator
<TElementIn
, TElementOut
, TResult
>.Validate(this.target
,
91 this.ExpectedElementCount
,
98 private static TResult
Validate(IEnumerable
<TElementIn
> argument
,
100 int expectedElementCount
,
102 Func
<TElementIn
, int, TElementOut
> map
,
103 Func
<List
<TElementOut
>, TResult
> collect
,
104 Func
<TElementIn
, int, string> deriveName
)
106 Debug
.Assert(map
!= null, "Set EnumerableValidator.ConvertElement before calling validate");
107 Debug
.Assert(collect
!= null, "Set EnumerableValidator.CreateResult before calling validate");
109 EntityUtil
.CheckArgumentNull(argument
, argumentName
);
111 bool checkNull
= (default(TElementIn
) == null);
112 bool checkCount
= (expectedElementCount
!= -1);
113 Dictionary
<string, int> nameIndex
= null;
114 if (deriveName
!= null)
116 nameIndex
= new Dictionary
<string, int>();
120 List
<TElementOut
> validatedElements
= new List
<TElementOut
>();
121 foreach (TElementIn elementIn
in argument
)
123 // More elements in 'arguments' than expected?
124 if (checkCount
&& pos
== expectedElementCount
)
126 throw EntityUtil
.Argument(System
.Data
.Entity
.Strings
.Cqt_ExpressionList_IncorrectElementCount
, argumentName
);
129 if (checkNull
&& elementIn
== null)
131 // Don't call FormatIndex unless an exception is actually being thrown
132 throw EntityUtil
.ArgumentNull(StringUtil
.FormatIndex(argumentName
, pos
));
135 TElementOut elementOut
= map(elementIn
, pos
);
136 validatedElements
.Add(elementOut
);
138 if (deriveName
!= null)
140 string name
= deriveName(elementIn
, pos
);
141 Debug
.Assert(name
!= null, "GetName should not produce null");
143 if (nameIndex
.TryGetValue(name
, out foundIndex
))
145 throw EntityUtil
.Argument(
146 System
.Data
.Entity
.Strings
.Cqt_Util_CheckListDuplicateName(foundIndex
, pos
, name
),
147 StringUtil
.FormatIndex(argumentName
, pos
)
150 nameIndex
[name
] = pos
;
156 // If an expected count was specified, the actual count must match
159 if (pos
!= expectedElementCount
)
161 throw EntityUtil
.Argument(System
.Data
.Entity
.Strings
.Cqt_ExpressionList_IncorrectElementCount
, argumentName
);
166 // No expected count was specified, simply verify empty vs. non-empty.
167 if (0 == pos
&& !allowEmpty
)
169 throw EntityUtil
.Argument(System
.Data
.Entity
.Strings
.Cqt_Util_CheckListEmptyInvalid
, argumentName
);
173 return collect(validatedElements
);