You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
624 lines
20 KiB
C#
624 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
#if UNITY_2018_1_OR_NEWER
|
|
using Unity.Collections;
|
|
#endif
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using Object = UnityEngine.Object;
|
|
#if UNITY_2018_3_OR_NEWER
|
|
using PrefabStage = UnityEditor.SceneManagement.PrefabStage;
|
|
using PrefabStageUtility = UnityEditor.SceneManagement.PrefabStageUtility;
|
|
#endif
|
|
|
|
namespace AssetUsageDetectorNamespace
|
|
{
|
|
public static class Utilities
|
|
{
|
|
// A set of commonly used Unity types
|
|
private static readonly HashSet<Type> primitiveUnityTypes = new HashSet<Type>()
|
|
{
|
|
typeof( string ), typeof( Vector4 ), typeof( Vector3 ), typeof( Vector2 ), typeof( Rect ),
|
|
typeof( Quaternion ), typeof( Color ), typeof( Color32 ), typeof( LayerMask ), typeof( Bounds ),
|
|
typeof( Matrix4x4 ), typeof( AnimationCurve ), typeof( Gradient ), typeof( RectOffset ),
|
|
typeof( bool[] ), typeof( byte[] ), typeof( sbyte[] ), typeof( char[] ), typeof( decimal[] ),
|
|
typeof( double[] ), typeof( float[] ), typeof( int[] ), typeof( uint[] ), typeof( long[] ),
|
|
typeof( ulong[] ), typeof( short[] ), typeof( ushort[] ), typeof( string[] ),
|
|
typeof( Vector4[] ), typeof( Vector3[] ), typeof( Vector2[] ), typeof( Rect[] ),
|
|
typeof( Quaternion[] ), typeof( Color[] ), typeof( Color32[] ), typeof( LayerMask[] ), typeof( Bounds[] ),
|
|
typeof( Matrix4x4[] ), typeof( AnimationCurve[] ), typeof( Gradient[] ), typeof( RectOffset[] ),
|
|
typeof( List<bool> ), typeof( List<byte> ), typeof( List<sbyte> ), typeof( List<char> ), typeof( List<decimal> ),
|
|
typeof( List<double> ), typeof( List<float> ), typeof( List<int> ), typeof( List<uint> ), typeof( List<long> ),
|
|
typeof( List<ulong> ), typeof( List<short> ), typeof( List<ushort> ), typeof( List<string> ),
|
|
typeof( List<Vector4> ), typeof( List<Vector3> ), typeof( List<Vector2> ), typeof( List<Rect> ),
|
|
typeof( List<Quaternion> ), typeof( List<Color> ), typeof( List<Color32> ), typeof( List<LayerMask> ), typeof( List<Bounds> ),
|
|
typeof( List<Matrix4x4> ), typeof( List<AnimationCurve> ), typeof( List<Gradient> ), typeof( List<RectOffset> ),
|
|
#if UNITY_2017_2_OR_NEWER
|
|
typeof( Vector3Int ), typeof( Vector2Int ), typeof( RectInt ), typeof( BoundsInt ),
|
|
typeof( Vector3Int[] ), typeof( Vector2Int[] ), typeof( RectInt[] ), typeof( BoundsInt[] ),
|
|
typeof( List<Vector3Int> ), typeof( List<Vector2Int> ), typeof( List<RectInt> ), typeof( List<BoundsInt> )
|
|
#endif
|
|
};
|
|
|
|
private static readonly string reflectionNamespace = typeof( Assembly ).Namespace;
|
|
#if UNITY_2018_1_OR_NEWER
|
|
private static readonly string nativeCollectionsNamespace = typeof( NativeArray<int> ).Namespace;
|
|
#endif
|
|
|
|
private static readonly HashSet<string> folderContentsSet = new HashSet<string>();
|
|
|
|
private static readonly StringBuilder stringBuilder = new StringBuilder( 10 );
|
|
|
|
#if UNITY_2018_3_OR_NEWER
|
|
private static int previousPingedPrefabInstanceId;
|
|
private static double previousPingedPrefabPingTime;
|
|
#endif
|
|
|
|
public static readonly GUILayoutOption GL_EXPAND_WIDTH = GUILayout.ExpandWidth( true );
|
|
public static readonly GUILayoutOption GL_EXPAND_HEIGHT = GUILayout.ExpandHeight( true );
|
|
public static readonly GUILayoutOption GL_WIDTH_25 = GUILayout.Width( 25 );
|
|
public static readonly GUILayoutOption GL_WIDTH_100 = GUILayout.Width( 100 );
|
|
public static readonly GUILayoutOption GL_WIDTH_250 = GUILayout.Width( 250 );
|
|
public static readonly GUILayoutOption GL_HEIGHT_0 = GUILayout.Height( 0 );
|
|
public static readonly GUILayoutOption GL_HEIGHT_2 = GUILayout.Height( 2 );
|
|
public static readonly GUILayoutOption GL_HEIGHT_30 = GUILayout.Height( 30 );
|
|
public static readonly GUILayoutOption GL_HEIGHT_35 = GUILayout.Height( 35 );
|
|
public static readonly GUILayoutOption GL_HEIGHT_40 = GUILayout.Height( 40 );
|
|
|
|
private static GUIStyle m_boxGUIStyle; // GUIStyle used to draw the results of the search
|
|
public static GUIStyle BoxGUIStyle
|
|
{
|
|
get
|
|
{
|
|
if( m_boxGUIStyle == null )
|
|
{
|
|
m_boxGUIStyle = new GUIStyle( EditorStyles.helpBox )
|
|
{
|
|
alignment = TextAnchor.MiddleCenter,
|
|
font = EditorStyles.label.font
|
|
};
|
|
|
|
Color textColor = GUI.skin.button.normal.textColor;
|
|
m_boxGUIStyle.normal.textColor = textColor;
|
|
m_boxGUIStyle.hover.textColor = textColor;
|
|
m_boxGUIStyle.focused.textColor = textColor;
|
|
m_boxGUIStyle.active.textColor = textColor;
|
|
|
|
m_boxGUIStyle.fontSize = ( m_boxGUIStyle.fontSize + GUI.skin.button.fontSize ) / 2;
|
|
}
|
|
|
|
return m_boxGUIStyle;
|
|
}
|
|
}
|
|
|
|
private static GUIStyle m_tooltipGUIStyle; // GUIStyle used to draw the tooltip
|
|
public static GUIStyle TooltipGUIStyle
|
|
{
|
|
get
|
|
{
|
|
GUIStyleState normalState;
|
|
|
|
if( m_tooltipGUIStyle != null )
|
|
normalState = m_tooltipGUIStyle.normal;
|
|
else
|
|
{
|
|
m_tooltipGUIStyle = new GUIStyle( EditorStyles.helpBox )
|
|
{
|
|
alignment = TextAnchor.MiddleCenter,
|
|
font = EditorStyles.label.font
|
|
};
|
|
|
|
normalState = m_tooltipGUIStyle.normal;
|
|
|
|
normalState.background = null;
|
|
normalState.scaledBackgrounds = new Texture2D[0];
|
|
normalState.textColor = Color.black;
|
|
}
|
|
|
|
if( normalState.background == null || normalState.background.Equals( null ) )
|
|
{
|
|
Texture2D backgroundTexture = new Texture2D( 1, 1 ) { hideFlags = HideFlags.HideAndDontSave };
|
|
backgroundTexture.SetPixel( 0, 0, new Color( 0.88f, 0.88f, 0.88f, 0.85f ) );
|
|
backgroundTexture.Apply();
|
|
|
|
normalState.background = backgroundTexture;
|
|
}
|
|
|
|
return m_tooltipGUIStyle;
|
|
}
|
|
}
|
|
|
|
// Get a unique-ish string hash code for an object
|
|
public static string Hash( this object obj )
|
|
{
|
|
if( obj is Object )
|
|
return obj.GetHashCode().ToString();
|
|
|
|
return obj.GetHashCode() + obj.GetType().Name;
|
|
}
|
|
|
|
// Check if object is an asset or a Scene object
|
|
public static bool IsAsset( this object obj )
|
|
{
|
|
return obj is Object && AssetDatabase.Contains( (Object) obj );
|
|
}
|
|
|
|
// Check if object is a folder asset
|
|
public static bool IsFolder( this Object obj )
|
|
{
|
|
return obj is DefaultAsset && AssetDatabase.IsValidFolder( AssetDatabase.GetAssetPath( obj ) );
|
|
}
|
|
|
|
// Returns an enumerator to iterate through all asset paths in the folder
|
|
public static IEnumerable<string> EnumerateFolderContents( Object folderAsset )
|
|
{
|
|
string[] folderContents = AssetDatabase.FindAssets( "", new string[] { AssetDatabase.GetAssetPath( folderAsset ) } );
|
|
if( folderContents == null )
|
|
return new EmptyEnumerator<string>();
|
|
|
|
folderContentsSet.Clear();
|
|
for( int i = 0; i < folderContents.Length; i++ )
|
|
{
|
|
string filePath = AssetDatabase.GUIDToAssetPath( folderContents[i] );
|
|
if( !string.IsNullOrEmpty( filePath ) && !AssetDatabase.IsValidFolder( filePath ) )
|
|
folderContentsSet.Add( filePath );
|
|
}
|
|
|
|
return folderContentsSet;
|
|
}
|
|
|
|
// Select an object in the editor
|
|
public static void SelectInEditor( this Object obj )
|
|
{
|
|
if( obj == null )
|
|
return;
|
|
|
|
Event e = Event.current;
|
|
|
|
// If CTRL or Shift keys are pressed, do a multi-select;
|
|
// otherwise select only the clicked object and ping it in editor
|
|
if( !e.control && !e.shift )
|
|
Selection.activeObject = obj.PingInEditor();
|
|
else
|
|
{
|
|
Component objAsComp = obj as Component;
|
|
GameObject objAsGO = obj as GameObject;
|
|
int selectionIndex = -1;
|
|
|
|
Object[] selection = Selection.objects;
|
|
for( int i = 0; i < selection.Length; i++ )
|
|
{
|
|
Object selected = selection[i];
|
|
|
|
// Don't allow including both a gameobject and one of its components in the selection
|
|
if( selected == obj || ( objAsComp != null && selected == objAsComp.gameObject ) ||
|
|
( objAsGO != null && selected is Component && ( (Component) selected ).gameObject == objAsGO ) )
|
|
{
|
|
selectionIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Object[] newSelection;
|
|
if( selectionIndex == -1 )
|
|
{
|
|
// Include object in selection
|
|
newSelection = new Object[selection.Length + 1];
|
|
selection.CopyTo( newSelection, 0 );
|
|
newSelection[selection.Length] = obj;
|
|
}
|
|
else
|
|
{
|
|
// Remove object from selection
|
|
newSelection = new Object[selection.Length - 1];
|
|
int j = 0;
|
|
for( int i = 0; i < selectionIndex; i++, j++ )
|
|
newSelection[j] = selection[i];
|
|
for( int i = selectionIndex + 1; i < selection.Length; i++, j++ )
|
|
newSelection[j] = selection[i];
|
|
}
|
|
|
|
Selection.objects = newSelection;
|
|
}
|
|
}
|
|
|
|
// Ping an object in either Project view or Hierarchy view
|
|
public static Object PingInEditor( this Object obj )
|
|
{
|
|
if( obj is Component )
|
|
obj = ( (Component) obj ).gameObject;
|
|
|
|
Object selection = obj;
|
|
|
|
// Pinging a prefab only works if the pinged object is the root of the prefab
|
|
// or a direct child of it. Pinging any grandchildren of the prefab
|
|
// does not work; in which case, traverse the parent hierarchy until
|
|
// a pingable parent is reached
|
|
if( obj.IsAsset() && obj is GameObject )
|
|
{
|
|
#if UNITY_2018_3_OR_NEWER
|
|
Transform objTR = ( (GameObject) obj ).transform.root;
|
|
|
|
PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType( objTR.gameObject );
|
|
if( prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant )
|
|
{
|
|
string assetPath = AssetDatabase.GetAssetPath( objTR.gameObject );
|
|
PrefabStage openPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
|
|
|
// Try to open the prefab stage of pinged prefabs if they are double clicked
|
|
if( previousPingedPrefabInstanceId == objTR.GetInstanceID() && EditorApplication.timeSinceStartup - previousPingedPrefabPingTime <= 0.3f &&
|
|
#if UNITY_2020_1_OR_NEWER
|
|
( openPrefabStage == null || !openPrefabStage.stageHandle.IsValid() || assetPath != openPrefabStage.assetPath ) )
|
|
#else
|
|
( openPrefabStage == null || !openPrefabStage.stageHandle.IsValid() || assetPath != openPrefabStage.prefabAssetPath ) )
|
|
#endif
|
|
{
|
|
AssetDatabase.OpenAsset( objTR.gameObject );
|
|
openPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
|
}
|
|
|
|
previousPingedPrefabInstanceId = objTR.GetInstanceID();
|
|
previousPingedPrefabPingTime = EditorApplication.timeSinceStartup;
|
|
|
|
#if UNITY_2020_1_OR_NEWER
|
|
if( openPrefabStage != null && openPrefabStage.stageHandle.IsValid() && assetPath == openPrefabStage.assetPath )
|
|
#else
|
|
if( openPrefabStage != null && openPrefabStage.stageHandle.IsValid() && assetPath == openPrefabStage.prefabAssetPath )
|
|
#endif
|
|
{
|
|
GameObject prefabStageGO = FollowSymmetricHierarchy( (GameObject) obj, ( (GameObject) obj ).transform.root.gameObject, openPrefabStage.prefabContentsRoot );
|
|
if( prefabStageGO != null )
|
|
{
|
|
objTR = prefabStageGO.transform;
|
|
selection = objTR.gameObject;
|
|
}
|
|
}
|
|
#if UNITY_2019_1_OR_NEWER
|
|
else if( obj != objTR.gameObject )
|
|
{
|
|
Debug.Log( "Open " + assetPath + " in prefab mode to select and edit " + obj.name );
|
|
selection = objTR.gameObject;
|
|
}
|
|
#else
|
|
else
|
|
Debug.Log( "Open " + assetPath + " in prefab mode to select and edit " + obj.name );
|
|
#endif
|
|
}
|
|
#else
|
|
Transform objTR = ( (GameObject) obj ).transform;
|
|
while( objTR.parent != null && objTR.parent.parent != null )
|
|
objTR = objTR.parent;
|
|
#endif
|
|
|
|
obj = objTR.gameObject;
|
|
}
|
|
|
|
EditorGUIUtility.PingObject( obj );
|
|
return selection;
|
|
}
|
|
|
|
// We are passing "go"s root Transform to thisRoot parameter. If we use go.transform.root instead, when we are in prefab mode on
|
|
// newer Unity versions, it points to the preview scene at the root of the prefab stage instead of pointing to the actual root of "go"
|
|
public static GameObject FollowSymmetricHierarchy( this GameObject go, GameObject thisRoot, GameObject symmetricRoot )
|
|
{
|
|
Transform target = go.transform;
|
|
Transform root1 = thisRoot.transform;
|
|
Transform root2 = symmetricRoot.transform;
|
|
while( root1 != target )
|
|
{
|
|
Transform temp = target;
|
|
while( temp.parent != root1 )
|
|
temp = temp.parent;
|
|
|
|
Transform newRoot2;
|
|
int siblingIndex = temp.GetSiblingIndex();
|
|
if( siblingIndex < root2.childCount )
|
|
{
|
|
newRoot2 = root2.GetChild( siblingIndex );
|
|
if( newRoot2.name != temp.name )
|
|
newRoot2 = root2.Find( temp.name );
|
|
}
|
|
else
|
|
newRoot2 = root2.Find( temp.name );
|
|
|
|
if( newRoot2 == null )
|
|
return null;
|
|
|
|
root2 = newRoot2;
|
|
root1 = temp;
|
|
}
|
|
|
|
return root2.gameObject;
|
|
}
|
|
|
|
// Check if the field is serializable
|
|
public static bool IsSerializable( this FieldInfo fieldInfo )
|
|
{
|
|
// See Serialization Rules: https://docs.unity3d.com/Manual/script-Serialization.html
|
|
if( fieldInfo.IsInitOnly )
|
|
return false;
|
|
|
|
#if UNITY_2019_3_OR_NEWER
|
|
// SerializeReference makes even System.Object fields serializable
|
|
if( Attribute.IsDefined( fieldInfo, typeof( SerializeReference ) ) )
|
|
return true;
|
|
#endif
|
|
|
|
if( ( !fieldInfo.IsPublic || fieldInfo.IsNotSerialized ) && !Attribute.IsDefined( fieldInfo, typeof( SerializeField ) ) )
|
|
return false;
|
|
|
|
return IsTypeSerializable( fieldInfo.FieldType );
|
|
}
|
|
|
|
// Check if the property is serializable
|
|
public static bool IsSerializable( this PropertyInfo propertyInfo )
|
|
{
|
|
return IsTypeSerializable( propertyInfo.PropertyType );
|
|
}
|
|
|
|
// Check if type is serializable
|
|
private static bool IsTypeSerializable( Type type )
|
|
{
|
|
// see Serialization Rules: https://docs.unity3d.com/Manual/script-Serialization.html
|
|
if( typeof( Object ).IsAssignableFrom( type ) )
|
|
return true;
|
|
|
|
if( type.IsArray )
|
|
{
|
|
if( type.GetArrayRank() != 1 )
|
|
return false;
|
|
|
|
type = type.GetElementType();
|
|
|
|
if( typeof( Object ).IsAssignableFrom( type ) )
|
|
return true;
|
|
}
|
|
else if( type.IsGenericType )
|
|
{
|
|
// Generic types are allowed on 2020.1 and later
|
|
#if UNITY_2020_1_OR_NEWER
|
|
if( type.GetGenericTypeDefinition() == typeof( List<> ) )
|
|
{
|
|
type = type.GetGenericArguments()[0];
|
|
|
|
if( typeof( Object ).IsAssignableFrom( type ) )
|
|
return true;
|
|
}
|
|
#else
|
|
if( type.GetGenericTypeDefinition() != typeof( List<> ) )
|
|
return false;
|
|
|
|
type = type.GetGenericArguments()[0];
|
|
|
|
if( typeof( Object ).IsAssignableFrom( type ) )
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
#if !UNITY_2020_1_OR_NEWER
|
|
if( type.IsGenericType )
|
|
return false;
|
|
#endif
|
|
|
|
return Attribute.IsDefined( type, typeof( SerializableAttribute ), false );
|
|
}
|
|
|
|
// Check if instances of this type should be searched for references
|
|
public static bool IsIgnoredUnityType( this Type type )
|
|
{
|
|
if( type.IsPrimitive || primitiveUnityTypes.Contains( type ) || type.IsEnum )
|
|
return true;
|
|
|
|
#if UNITY_2018_1_OR_NEWER
|
|
// Searching NativeArrays for reference can throw InvalidOperationException if the collection is disposed
|
|
if( type.Namespace == nativeCollectionsNamespace )
|
|
return true;
|
|
#endif
|
|
|
|
// Searching assembly variables for reference throws InvalidCastException on .NET 4.0 runtime
|
|
if( typeof( Type ).IsAssignableFrom( type ) || type.Namespace == reflectionNamespace )
|
|
return true;
|
|
|
|
// Searching pointers or ref variables for reference throws ArgumentException
|
|
if( type.IsPointer || type.IsByRef )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Get <get> function for a field
|
|
public static VariableGetVal CreateGetter( this FieldInfo fieldInfo, Type type )
|
|
{
|
|
// Commented the IL generator code below because it might actually be slower than simply using reflection
|
|
// Credit: https://www.codeproject.com/Articles/14560/Fast-Dynamic-Property-Field-Accessors
|
|
//DynamicMethod dm = new DynamicMethod( "Get" + fieldInfo.Name, fieldInfo.FieldType, new Type[] { typeof( object ) }, type );
|
|
//ILGenerator il = dm.GetILGenerator();
|
|
//// Load the instance of the object (argument 0) onto the stack
|
|
//il.Emit( OpCodes.Ldarg_0 );
|
|
//// Load the value of the object's field (fi) onto the stack
|
|
//il.Emit( OpCodes.Ldfld, fieldInfo );
|
|
//// return the value on the top of the stack
|
|
//il.Emit( OpCodes.Ret );
|
|
|
|
//return (VariableGetVal) dm.CreateDelegate( typeof( VariableGetVal ) );
|
|
|
|
return fieldInfo.GetValue;
|
|
}
|
|
|
|
// Get <get> function for a property
|
|
public static VariableGetVal CreateGetter( this PropertyInfo propertyInfo )
|
|
{
|
|
// Can't use PropertyWrapper (which uses CreateDelegate) for property getters of structs
|
|
if( propertyInfo.DeclaringType.IsValueType )
|
|
{
|
|
return !propertyInfo.CanRead ? (VariableGetVal) null : ( obj ) =>
|
|
{
|
|
try
|
|
{
|
|
return propertyInfo.GetValue( obj, null );
|
|
}
|
|
catch
|
|
{
|
|
// Property getters may return various kinds of exceptions if their backing fields are not initialized (yet)
|
|
return null;
|
|
}
|
|
};
|
|
}
|
|
|
|
Type GenType = typeof( PropertyWrapper<,> ).MakeGenericType( propertyInfo.DeclaringType, propertyInfo.PropertyType );
|
|
return ( (IPropertyAccessor) Activator.CreateInstance( GenType, propertyInfo.GetGetMethod( true ) ) ).GetValue;
|
|
}
|
|
|
|
// Check if all open scenes are saved (not dirty)
|
|
public static bool AreScenesSaved()
|
|
{
|
|
for( int i = 0; i < SceneManager.loadedSceneCount; i++ )
|
|
{
|
|
Scene scene = EditorSceneManager.GetSceneAt( i );
|
|
if( scene.isDirty || string.IsNullOrEmpty( scene.path ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Returns file extension in lowercase (period not included)
|
|
public static string GetFileExtension( string path )
|
|
{
|
|
int extensionIndex = path.LastIndexOf( '.' );
|
|
if( extensionIndex < 0 || extensionIndex >= path.Length - 1 )
|
|
return "";
|
|
|
|
stringBuilder.Length = 0;
|
|
for( extensionIndex++; extensionIndex < path.Length; extensionIndex++ )
|
|
{
|
|
char ch = path[extensionIndex];
|
|
if( ch >= 65 && ch <= 90 ) // A-Z
|
|
ch += (char) 32; // Converted to a-z
|
|
|
|
stringBuilder.Append( ch );
|
|
}
|
|
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
// Draw horizontal line inside OnGUI
|
|
public static void DrawSeparatorLine()
|
|
{
|
|
GUILayout.Space( 4 );
|
|
GUILayout.Box( "", GL_HEIGHT_2, GL_EXPAND_WIDTH );
|
|
GUILayout.Space( 4 );
|
|
}
|
|
|
|
// Check if all the objects inside the list are null
|
|
public static bool IsEmpty( this List<ObjectToSearch> objectsToSearch )
|
|
{
|
|
if( objectsToSearch == null )
|
|
return true;
|
|
|
|
for( int i = 0; i < objectsToSearch.Count; i++ )
|
|
{
|
|
if( objectsToSearch[i].obj != null && !objectsToSearch[i].obj.Equals( null ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Check if all the objects inside the list are null
|
|
public static bool IsEmpty( this List<Object> objects )
|
|
{
|
|
if( objects == null )
|
|
return true;
|
|
|
|
for( int i = 0; i < objects.Count; i++ )
|
|
{
|
|
if( objects[i] != null && !objects[i].Equals( null ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Check if all the objects that are enumerated are null
|
|
public static bool IsEmpty( this IEnumerable<Object> objects )
|
|
{
|
|
if( objects == null )
|
|
return true;
|
|
|
|
using( IEnumerator<Object> enumerator = objects.GetEnumerator() )
|
|
{
|
|
while( enumerator.MoveNext() )
|
|
{
|
|
if( enumerator.Current != null && !enumerator.Current.Equals( null ) )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Returns true is str starts with prefix
|
|
public static bool StartsWithFast( this string str, string prefix )
|
|
{
|
|
int aLen = str.Length;
|
|
int bLen = prefix.Length;
|
|
int ap = 0; int bp = 0;
|
|
while( ap < aLen && bp < bLen && str[ap] == prefix[bp] )
|
|
{
|
|
ap++;
|
|
bp++;
|
|
}
|
|
|
|
return bp == bLen;
|
|
}
|
|
|
|
// Returns true is str ends with postfix
|
|
public static bool EndsWithFast( this string str, string postfix )
|
|
{
|
|
int ap = str.Length - 1;
|
|
int bp = postfix.Length - 1;
|
|
while( ap >= 0 && bp >= 0 && str[ap] == postfix[bp] )
|
|
{
|
|
ap--;
|
|
bp--;
|
|
}
|
|
|
|
return bp < 0;
|
|
}
|
|
|
|
public static bool ContainsFast<T>( this List<T> list, T element )
|
|
{
|
|
if( !( element is ValueType ) )
|
|
{
|
|
for( int i = list.Count - 1; i >= 0; i-- )
|
|
{
|
|
if( ReferenceEquals( list[i], element ) )
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( int i = list.Count - 1; i >= 0; i-- )
|
|
{
|
|
if( element.Equals( list[i] ) )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static void RemoveAtFast<T>( this List<T> list, int index )
|
|
{
|
|
int lastElementIndex = list.Count - 1;
|
|
|
|
list[index] = list[lastElementIndex];
|
|
list.RemoveAt( lastElementIndex );
|
|
}
|
|
}
|
|
} |