IEnumerable로 단일 항목 전달
매개 변수 T
를 예상하는 메소드에 단일 유형의 항목을 전달하는 일반적인 방법이 IEnumerable<T>
있습니까? 언어는 C #, 프레임 워크 버전 2.0입니다.
현재 도우미 메서드를 사용하고 있습니다 (.Net 2.0이므로 LINQ와 비슷한 캐스팅 / 프로젝션 도우미 메서드가 많이 있습니다). 그러나 이것은 어리석은 것처럼 보입니다.
public static class IEnumerableExt
{
// usage: IEnumerableExt.FromSingleItem(someObject);
public static IEnumerable<T> FromSingleItem<T>(T item)
{
yield return item;
}
}
다른 방법은 물론 만들고 채우는 것 List<T>
또는를 Array
하고 대신 전달할 IEnumerable<T>
.
[편집] 확장 방법으로 이름을 지정할 수 있습니다 :
public static class IEnumerableExt
{
// usage: someObject.SingleItemAsEnumerable();
public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
{
yield return item;
}
}
여기에 뭔가 빠졌습니까?
[Edit2] 우리는 someObject.Yield()
(아래 주석에서 @Peter가 제안한 바와 같이)이 확장 방법의 가장 좋은 이름, 주로 간결함을 찾았 으므로 누군가가 그것을 잡고 싶다면 XML 주석과 함께 있습니다.
public static class IEnumerableExt
{
/// <summary>
/// Wraps this object instance into an IEnumerable<T>
/// consisting of a single item.
/// </summary>
/// <typeparam name="T"> Type of the object. </typeparam>
/// <param name="item"> The instance that will be wrapped. </param>
/// <returns> An IEnumerable<T> consisting of a single item. </returns>
public static IEnumerable<T> Yield<T>(this T item)
{
yield return item;
}
}
도우미 방법은 IMO를 수행하는 가장 깨끗한 방법입니다. 목록이나 배열을 전달하면 부주의 한 코드 조각이이를 캐스팅하여 내용을 변경하여 일부 상황에서 이상한 동작을 일으킬 수 있습니다. 읽기 전용 컬렉션을 사용할 수 있지만 더 많은 줄 바꿈이 필요할 수 있습니다. 귀하의 솔루션은 최대한 깔끔하다고 생각합니다.
글쎄, 메소드 IEnumerable
가 하나의 요소 만 포함하더라도 목록 인 무언가를 전달해야한다고 기대 한다면.
통과
new T[] { item }
논쟁이 충분해야한다고 생각합니다
C # 3.0에서는 System.Linq.Enumerable 클래스를 활용할 수 있습니다.
// using System.Linq
Enumerable.Repeat(item, 1);
그러면 아이템 만 포함하는 새로운 IEnumerable이 생성됩니다.
C # 3 (2라고 말했듯이)에서 구문을 좀 더 수용 할 수있는 일반 확장 방법을 작성할 수 있습니다.
static class IEnumerableExtensions
{
public static IEnumerable<T> ToEnumerable<T>(this T item)
{
yield return item;
}
}
클라이언트 코드는 item.ToEnumerable()
입니다.
이 도우미 방법은 항목 또는 많은 사람에게 효과적입니다.
public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
return items;
}
클라이언트 API를 단순화하기 위해 T 유형의 인수로 메소드의 새로운 과부하를 제안한 사람이 아무도 없습니다.
public void DoSomething<T>(IEnumerable<T> list)
{
// Do Something
}
public void DoSomething<T>(T item)
{
DoSomething(new T[] { item });
}
이제 클라이언트 코드는 다음과 같이 할 수 있습니다.
MyItem item = new MyItem();
Obj.DoSomething(item);
또는 목록 :
List<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);
방금 찾은 사용자 LukeH가 제안한 것을 보았을 때 이것을 수행하는 좋은 간단한 방법은 다음과 같습니다.
public static void PerformAction(params YourType[] items)
{
// Forward call to IEnumerable overload
PerformAction(items.AsEnumerable());
}
public static void PerformAction(IEnumerable<YourType> items)
{
foreach (YourType item in items)
{
// Do stuff
}
}
이 패턴을 사용하면 여러 가지 방법으로 동일한 기능을 호출 할 수 있습니다. 단일 항목; 여러 항목 (쉼표로 구분); 배열; 목록; 열거 등
그래도 AsEnumerable 방법을 사용하는 효율성에 대해서는 100 % 확신 할 수 없지만 잘 작동합니다.
업데이트 : AsEnumerable 기능이 매우 효율적으로 보입니다! ( 참고 )
한 가지 방법으로는 너무 과잉이지만 일부 사람들은 Interactive Extensions가 유용하다고 생각합니다.
Microsoft의 대화식 확장 (Ix)에는 다음 방법이 포함됩니다.
public static IEnumerable<TResult> Return<TResult>(TResult value)
{
yield return value;
}
다음과 같이 활용할 수 있습니다.
var result = EnumerableEx.Return(0);
Ix는 원래 Linq 확장 방법에없는 새로운 기능을 추가하며 Rx (Reactive Extensions)를 생성 한 직접적인 결과입니다.
Linq Extension Methods
+ Ix
= Rx
용으로 생각하십시오 IEnumerable
.
CodePlex에서 Rx와 Ix를 모두 찾을 수 있습니다 .
(이전에 말했듯이)
MyMethodThatExpectsAnIEnumerable(new[] { myObject });
또는
MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1));
참고로, 익명 객체의 빈 목록을 원할 경우 마지막 버전도 좋습니다.
var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));
이 30 % 더 빠른 것보다 yield
나 Enumerable.Repeat
에 사용하는 경우 foreach
로 인해 이 C # 컴파일러 최적화 하고, 다른 경우에는 동일한 성능의.
public struct SingleSequence<T> : IEnumerable<T> {
public struct SingleEnumerator : IEnumerator<T> {
private readonly SingleSequence<T> _parent;
private bool _couldMove;
public SingleEnumerator(ref SingleSequence<T> parent) {
_parent = parent;
_couldMove = true;
}
public T Current => _parent._value;
object IEnumerator.Current => Current;
public void Dispose() { }
public bool MoveNext() {
if (!_couldMove) return false;
_couldMove = false;
return true;
}
public void Reset() {
_couldMove = true;
}
}
private readonly T _value;
public SingleSequence(T value) {
_value = value;
}
public IEnumerator<T> GetEnumerator() {
return new SingleEnumerator(ref this);
}
IEnumerator IEnumerable.GetEnumerator() {
return new SingleEnumerator(ref this);
}
}
이 테스트에서 :
// Fastest among seqs, but still 30x times slower than direct sum
// 49 mops vs 37 mops for yield, or c.30% faster
[Test]
public void SingleSequenceStructForEach() {
var sw = new Stopwatch();
sw.Start();
long sum = 0;
for (var i = 0; i < 100000000; i++) {
foreach (var single in new SingleSequence<int>(i)) {
sum += single;
}
}
sw.Stop();
Console.WriteLine($"Elapsed {sw.ElapsedMilliseconds}");
Console.WriteLine($"Mops {100000.0 / sw.ElapsedMilliseconds * 1.0}");
}
이것은 더 나을 수는 없지만 멋지다.
Enumerable.Range(0, 1).Select(i => item);
원래 게시물에 대한 @EarthEngine의 의견에 동의합니다. 즉 'AsSingleton'이 더 나은 이름입니다. 이 위키 백과 항목을 참조하십시오 . 그런 다음 싱글 톤의 정의에서 null 값이 인수로 전달되면 'AsSingleton'은 빈 IEnumerable 대신 단일 null 값으로 IEnumerable을 반환해야 if (item == null) yield break;
논쟁을 해결할 수 있습니다. 가장 좋은 해결책은 'AsSingleton'과 'AsSingletonOrEmpty'; 여기서 null이 인수로 전달되는 경우 'AsSingleton'은 단일 null 값을 반환하고 'AsSingletonOrEmpty'는 빈 IEnumerable을 반환합니다. 이처럼 :
public static IEnumerable<T> AsSingletonOrEmpty<T>(this T source)
{
if (source == null)
{
yield break;
}
else
{
yield return source;
}
}
public static IEnumerable<T> AsSingleton<T>(this T source)
{
yield return source;
}
그런 다음 IEnumerable의 'First'및 'FirstOrDefault'확장 메소드와 비슷하거나 비슷합니다.
IanG 는 이 주제에 대해 좋은 게시물을 가지고 있는데 , EnumerableFrom()
그 이름으로 제안한 것으로서 Haskell과 Rx 가이 를 언급한다고합니다 Return
.
IIRC F #은 그것을 Return이라고 부릅니다
. F # Seq
은 연산자를 호출합니다singleton<'T>
.
C # 중심이 될 준비가 되었다면 유혹하는 것은 그것을 실현 Yield
하는 yield return
데 관련된 것을 암시하는 것입니다.
퍼포먼스 측면에 관심이 있다면 James Michael Hare는 0 또는 1 개의 게시물을 반환합니다 .
내가 말한 가장 쉬운 방법은 다음과 같습니다 new T[]{item};
. 이를 수행 할 구문이 없습니다. 내가 생각할 수있는 가장 가까운 것은 params
키워드이지만 물론 메소드 정의에 액세스해야하며 배열에서만 사용할 수 있습니다.
Enumerable.Range(1,1).Select(_ => {
//Do some stuff... side effects...
return item;
});
위의 코드는 다음을 사용할 때 유용합니다
var existingOrNewObject = MyData.Where(myCondition)
.Concat(Enumerable.Range(1,1).Select(_ => {
//Create my object...
return item;
})).Take(1).First();
위의 코드 스 니펫에는 빈 / null 검사가 없으며 예외를 두려워하지 않고 하나의 객체 만 반환되도록 보장합니다. 또한 게으 르기 때문에 기준에 맞는 기존 데이터가 없음이 입증 될 때까지 클로저가 실행되지 않습니다.
나는 파티에 조금 늦었지만 어쨌든 내 길을 공유 할 것입니다. 내 문제는 ItemSource 또는 WPF TreeView를 단일 개체에 바인딩하려고한다는 것입니다. 계층 구조는 다음과 같습니다.
프로젝트> 도표> 방
항상 하나의 프로젝트 만 있었지만 제안 된 것처럼 하나의 객체 만 포함 된 컬렉션을 전달하지 않고도 트리에 프로젝트를 표시하고 싶었습니다.
IEnumerable 객체 만 ItemSource로 전달할 수 있으므로 클래스 IEnumerable을 만들기로 결정했습니다.
public class ProjectClass : IEnumerable<ProjectClass>
{
private readonly SingleItemEnumerator<AufmassProjekt> enumerator;
...
public IEnumerator<ProjectClass > GetEnumerator() => this.enumerator;
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
그리고 그에 따라 내 자신의 열거자를 만듭니다.
public class SingleItemEnumerator : IEnumerator
{
private bool hasMovedOnce;
public SingleItemEnumerator(object current)
{
this.Current = current;
}
public bool MoveNext()
{
if (this.hasMovedOnce) return false;
this.hasMovedOnce = true;
return true;
}
public void Reset()
{ }
public object Current { get; }
}
public class SingleItemEnumerator<T> : IEnumerator<T>
{
private bool hasMovedOnce;
public SingleItemEnumerator(T current)
{
this.Current = current;
}
public void Dispose() => (this.Current as IDisposable).Dispose();
public bool MoveNext()
{
if (this.hasMovedOnce) return false;
this.hasMovedOnce = true;
return true;
}
public void Reset()
{ }
public T Current { get; }
object IEnumerator.Current => this.Current;
}
이것은 아마도 "가장 깨끗한"해결책은 아니지만 저에게 효과적이었습니다.
편집 @ Groo가 지적한 것처럼 단일 책임 원칙
을 유지하기 위해 새로운 래퍼 클래스를 만들었습니다.
public class SingleItemWrapper : IEnumerable
{
private readonly SingleItemEnumerator enumerator;
public SingleItemWrapper(object item)
{
this.enumerator = new SingleItemEnumerator(item);
}
public object Item => this.enumerator.Current;
public IEnumerator GetEnumerator() => this.enumerator;
}
public class SingleItemWrapper<T> : IEnumerable<T>
{
private readonly SingleItemEnumerator<T> enumerator;
public SingleItemWrapper(T item)
{
this.enumerator = new SingleItemEnumerator<T>(item);
}
public T Item => this.enumerator.Current;
public IEnumerator<T> GetEnumerator() => this.enumerator;
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
내가 이렇게 사용
TreeView.ItemSource = new SingleItemWrapper(itemToWrap);
편집 2
나는 MoveNext()
방법 으로 실수를 수정했습니다 .
참고 URL : https://stackoverflow.com/questions/1577822/passing-a-single-item-as-ienumerablet
'Programming' 카테고리의 다른 글
$ rootScope. $ broadcast vs. $ scope. $ emit (0) | 2020.03.01 |
---|---|
ViewModel의 INotifyPropertyChanged와 DependencyProperty (0) | 2020.03.01 |
Eclipse-행 번호 속성이 누락되어 중단 점을 설치할 수 없습니다. (0) | 2020.03.01 |
JavaScript, Node.js : Array.forEach가 비동기식입니까? (0) | 2020.03.01 |
FragmentPagerAdapter와 FragmentStatePagerAdapter의 차이점은 무엇입니까? (0) | 2020.03.01 |